聯賽模擬測試18 A. 施工 單調佇列(棧)優化DP

liuchanglc發表於2020-10-17

題目描述



分析

  • 對於 \(Subtask\ 1\),可以寫一個 \(n^3\)\(DP\)\(f[i][j]\) 代表第 \(i\) 個建築高度為 \(j\) 時的最小花費,隨便轉移即可
    時間複雜度 \(O(n \times h^2)\)
  • 對於 \(Subtask\ 2\),我們沿用 \(Subtask\ 1\)的思路,記錄字首字尾 \(min\),將複雜度優化至 \(O(n \times h)\)
    但是顯然兩維的定義無法繼續進行優化,我們可以考慮改變一下定義的方式
    \(f[i]\) 表示考慮前 \(i\) 個建築,並且第 \(i\) 個建築高度不變的最優答案
    可以發現,列舉兩個不變的邊界,那麼中間的建築必定被提高成相同的小於等於邊界的高度
    也就是說我們需要把一些坑填平
    因為增加峰的高度既花人力又不能提高觀賞度
    增加坡的高度花人力但不能提高觀賞度
    只有增加坑的高度才會有貢獻,而且增加的值不能超過邊界的高度
    因為超過邊界的高度又變成了峰
    因此我們可以列舉邊界然後再列舉填平的高度
    轉移方程為 \(f[i]=f[j]+(sum2[i-1]-sum2[j])+(i-j-1)*h*h-(sum1[i-1]-sum1[j])*2*h+c*abs(a[i]+a[j]-2*h)\)
    其中 \(sum1[i]\)\(a\) 陣列的字首和,\(sum2[i]\)\(a\) 陣列平方的字首和
    上面的式子是展開後的式子,原式子並不難推
    注意這種做法我們需要把 \(a[0]\)\(a[n+1]\) 置為無窮大,因為我們有可能提高第一個和最後一個建築的高度
    要特判 \(i,j\) 等於 \(0\)\(n+1\) 的情況
    最後的答案為 \(f[n+1]\)
    時間複雜度 \(O(n^2 \times h)\),不知道為什麼能過這個子任務
  • 對於 \(Subtask\ 3\),我們把一維 \(DP\) 的狀態轉移方程化簡得到
    \(f[i]=f[j]+(i-j-1)*h*h-2*(sum1[i-1]-sum1[j]+c)*h+(sum2[i-1]-sum2[j])+c*(a[i]+a[j])\)
    我們發現前半部分是一個關於高度 \(h\) 的二次函式
    可以直接由對稱軸求出最小值
    時間複雜度 \(O(n^2)\)
  • 對於 \(Subtask\ 4\)\(Subtask\ 5\),我們會發現只有兩個高度較大的建築夾著一堆高度較小的建築才有貢獻
    因此可以用單調佇列(棧)維護
    時間複雜度 \(O(n logn)\)
    複雜度的瓶頸在\(ST\) 表查詢最值上

程式碼

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
typedef long long ll;
const int maxn=1e6+5;
int n,c,a[maxn],maxh,lg[maxn],st[maxn][22],head,tail,q[maxn];
ll f[maxn],sum1[maxn],sum2[maxn];
int zhao(ll i,ll j){
	if(j==0 && i==n+1) return ((double)(sum1[i-1]-sum1[j])/(double)(i-j-1)+0.5);
	else if(j==0 || i==n+1) return ((double)(2*sum1[i-1]-2*sum1[j]+c)/(double)(2.0*(i-j-1))+0.5);
	return ((double)(c+sum1[i-1]-sum1[j])/(double)(i-j-1)+0.5);
}
int cx(int l,int r){
	rg int k=lg[r-l+1];
	return std::max(st[l][k],st[r-(1<<k)+1][k]);
}
int main(){
	memset(f,0x3f,sizeof(f));
	n=read(),c=read();
	for(rg int i=1;i<=n;i++){
		a[i]=read();
		maxh=std::max(maxh,a[i]);
		sum1[i]=sum1[i-1]+a[i];
		sum2[i]=sum2[i-1]+1LL*a[i]*a[i];
		st[i][0]=a[i];
	}
	for(rg int i=2;i<=n;i++){
		lg[i]=lg[i/2]+1;
	}
	for(rg int j=1;j<=20;j++){
		for(rg int i=1;i+(1<<j)-1<=n;i++){
			st[i][j]=std::max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
		}
	}
	a[0]=a[n+1]=0x3f3f3f3f;
	f[0]=f[1]=0;
	head=1,tail=1;
	rg int mmax,mmin,now;
	for(rg int i=1;i<=n+1;i++){
		while(head<=tail){
			if(q[tail]==i-1){
				if(i<=n) f[i]=std::min(f[i],f[q[tail]]+1LL*c*std::abs(a[i]-a[i-1]));
				else f[i]=f[i-1];
			} else {
				mmax=cx(q[tail]+1,i-1);
				mmin=std::min(a[i],a[q[tail]]);
				if(mmin>=mmax){
					now=zhao(i,q[tail]);
					if(now<mmax) now=mmax;
					if(now>mmin) now=mmin;
					if(q[tail]==0 && i==n+1) f[i]=std::min(f[i],f[q[tail]]+1LL*(i-q[tail]-1)*now*now-2LL*(sum1[i-1]-sum1[q[tail]])*now+1LL*(sum2[i-1]-sum2[q[tail]]));
					else if(q[tail]==0) f[i]=std::min(f[i],f[q[tail]]+1LL*(i-q[tail]-1)*now*now-1LL*(2*sum1[i-1]-2*sum1[q[tail]]+c)*now+1LL*(sum2[i-1]-sum2[q[tail]])+1LL*c*a[i]);
					else if(i==n+1) f[i]=std::min(f[i],f[q[tail]]+1LL*(i-q[tail]-1)*now*now-1LL*(2*sum1[i-1]-2*sum1[q[tail]]+c)*now+1LL*(sum2[i-1]-sum2[q[tail]])+1LL*c*a[q[tail]]);
					else f[i]=std::min(f[i],f[q[tail]]+1LL*(i-q[tail]-1)*now*now-2LL*(sum1[i-1]-sum1[q[tail]]+c)*now+1LL*(sum2[i-1]-sum2[q[tail]])+1LL*c*(a[i]+a[q[tail]]));
				}
			}
			if(a[i]>=a[q[tail]])tail--;
			else break;
		}
		q[++tail]=i;
	}
	printf("%lld\n",f[n+1]);
	return 0;
}

相關文章