施工
题解
应该是很容易想到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(t−hj)2+2c(hi+hj−2t)
而上式可以转化成一个关于
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
*/