[雅礼集训2018]施工

施工

题解

应该是很容易想到dp的。

明显,如果一个楼房要增长高度的话,它两边的楼房都需要比他高,否则是不优的。但这个高可能是在前面增长后得到的。但我们发现,如果要增长的话,夹在最优解两个端点间的一段一定是高度相同的,否则一定可以把这个涨上去。

于是我们设 d p i dp_{i} dpi为对于前i个点保持第 i i i个点不变时的答案。
有转移方程式
d p i = ∑ k = i + 1 j ( t − h j ) 2 + 2 c ( h i + h j − 2 t ) dp_{i}=\sum_{k=i+1}^{j} (t-h_{j})^2+2c(h_{i}+h_{j}-2t) dpi=k=i+1j(thj)2+2c(hi+hj2t)
而上式可以转化成一个关于 t t t的二次函数,可以 O ( 1 ) O(1) O(1)求出它的对称轴。

但这样 O ( n 2 ) O(n^2) O(n2)的算法是明显不能过的。
我们发现能转移到点 i i i的点 j j j一定满足 h i , h j h_{i},h_{j} hi,hj是大于 i i i j j j之间任意一个点的,于是我们可以通过一个单调数据结构对其进行维护。

时间复杂度 O ( ) O() O()

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 1000005
typedef long long LL;
const LL INF=0x7f7f7f7f;
typedef pair<int,int> pii;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
LL n,c,sta[MAXN],stak;
LL dp[MAXN],sum[MAXN],sum1[MAXN],h[MAXN];
LL solve(int l,int r,LL w){
	LL a=r-l-1LL,b=2ll*(sum[r-1]-sum[l]);if(l)b+=1ll*c;if(r<=n)b+=1ll*c;
	LL C=sum1[r-1]-sum1[l];if(l)C+=1ll*c*h[l];if(r<=n)C+=1ll*c*h[r];
	LL tmp=max(w,(LL)(1.0*b/(2.0*a)+0.5));
	if(l)tmp=min(tmp,h[l]);if(r<=n)tmp=min(tmp,h[r]);
	//printf("%lld %lld %lld\n",l,r,tmp);
	return a*tmp*tmp-b*tmp+C;
}
signed main(){
	freopen("construct.in","r",stdin);
	freopen("construct.out","w",stdout);
	read(n);read(c);h[0]=h[n+1]=INF;sta[++stak]=0;
	for(int i=1;i<=n;i++)read(h[i]),sum[i]=sum[i-1]+1ll*h[i],sum1[i]=sum1[i-1]+1ll*h[i]*h[i];
	for(int i=1;i<=n+1;i++){
		dp[i]=dp[i-1]+((i==1||i==n+1)?0LL:1ll*Fabs(h[i]-h[i-1])*c);
		while(stak&&h[sta[stak]]<=h[i]){
			if(stak>1)dp[i]=min(dp[i],dp[sta[stak-1]]+solve(sta[stak-1],i,h[sta[stak]]));
			stak--;
		}
		sta[++stak]=i;
		//printf("%lld:%lld\n",i,dp[i]);
	}
	printf("%lld\n",dp[n+1]);
	return 0;
}
/*
(x2+x1-2t)*c+t*t
t*t-2ct
*/

谢谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值