1. 程式人生 > >雅禮集訓 Day2 T1 施工

雅禮集訓 Day2 T1 施工

首先有一個比較好想的結論(誰說的。。。一點都不好想!)就是最優的情況一定是有兩端高於中間的一段平地。

因為一段本來有高度差的一起增高等於沒用,所以我們可以把最終高度相等的作為一段,無論這段有多少個。

而這樣做的條件是兩邊比中間高。

這樣我們得到一個dp式子f_{i}=\sum_{k=j+1}^{i-1}*(t-val_{k})^{2}+c(h_{i}+h_{j}-2*t),這個是個二次函式,可以直接求出t的最小值

就是理想高度,這樣就求出了最小的f_{i}來更新,而對於j的選取,就是在i前面第一個比它高的為止,因為維護一個單調遞減的

序列,如果拿出的點比i小,那麼因為這個點前面的比這個點大(單調遞減),所以可以作為j和i作為一組,當這個點比i高時

就不合法了 ,用單調佇列可以優化掉正常要列舉的三維。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int n;
ll c;
ll val[1000006];
ll ss[2][1000006];
int stak[1000006],top;
ll dp[1000006];
ll gett(int x,int y,ll mx)
{
	ll a=y-x-1;
	ll b=-2*(ss[0][y-1]-ss[0][x])-(x!=0)*1ll*c-(y!=n+1)*1ll*c;
	ll cc=ss[1][y-1]-ss[1][x]+(x!=0)*1ll*val[x]*c+(y!=n+1)*1ll*val[y]*c;
	ll t=-1*(ll)floor((double)b/((double)a*2.0)+0.5);
	t=max(t,mx);
	if(x!=0)t=min(t,val[x]);
	if(y!=n+1)t=min(t,val[y]);
	return a*t*t+b*t+cc;
}
int main()
{
	freopen("construct.in","r",stdin);
	freopen("construct.out","w",stdout);
	scanf("%d%I64d",&n,&c);
	for(int i=1;i<=n;i++)
	{
		scanf("%I64d",&val[i]);
		ss[0][i]=ss[0][i-1]+val[i];
		ss[1][i]=ss[1][i-1]+val[i]*val[i];
	}
	val[0]=val[n+1]=0x3f3f3f3f;
	stak[top++]=0;
	for(int i=1;i<=n+1;i++)
	{
		//dp[i]=dp[i-1]+(i==1||i==n+1)?0:abs(val[i]-val[i-1])*c;有毒的三步運算子 
		if(i==1||i==n+1)
		{
			dp[i]=dp[i-1];
		}else
		{
			dp[i]=dp[i-1]+abs(val[i]-val[i-1])*c;
		}
		while(top>0&&val[stak[top-1]]<=val[i])
		{
			if(top>1)
			{
				dp[i]=min(dp[i],dp[stak[top-2]]+gett(stak[top-2],i,val[stak[top-1]]));
			}
			top--;
		}
		stak[top++]=i;
	}
	printf("%I64d",dp[n+1]);
	return 0;
}