【传送门:BZOJ1010】
简要题意:
给出n条连续线段,每条线段都有长度为x[i],我们可以把连续若干条线段连在一起,变成一个组合,两条线段如果相连,就要在两条线段中间添加一个长度为1的格子(如果没有相连就不用添加),假如我们现在选择把第i条到第j条线段之间的所有线段变成一组合的话,这个组合的总长度就为:x[i]+x[i+1]+x[i+2]+x[i+3]+...+x[j]+j-i,现在给出一个常数L,假设当前选择的组合的长度为s,那么这个组合就为我们产生了(s-L)^2的费用,求出把n条线段分成若干组合所需要的最小费用,单独的线段可以成为一个组合
题解:
首先可以敲出一个DP,f[i]表示到第i个线段时,将1到i个线段都分成若干个组合的最小值
显然这个DP的时间复杂度是O(n^2),会超时
我们就用斜率优化来优化
首先处理n条线段,因为只有连续的线段才能成为一个组合,所以我们用sum[i]表示1到i条线段的总长度(先不加上线段之间的格子)
为什么要定义sum数组呢,因为我们处理了sum数组之后就可以很轻松的求出把i到j条线段变成一个组合的长度就是(sum[j]-sum[i]+i-j+1)
那么现在先写出DP方程:
f[i]=min(f[j]+(sum[i]-sum[j]+i-j-1-L)^2)
=min(f[j]+(sum[i]+i-(sum[j]+j)-(1+L))^2) 那么我们就可以发现sum数组一定会加上它所对应的位置,所以定义一个s数组表示sum数组加上它所对应的位置(如:s[i]=sum[i]+i),然后把L+1
首先我们先来证明决策单调性
假设j1<j2<i,在状态i处的j2决策不比j1决策差(心里想着淘汰j1),
即要满足:f[j2]+(s[i]-s[j2]-L)^2<f[j1]+(s[i]-s[j1]-L)^2
则对于i后的所有状态t,是否j2也不比就j1差?(术语:证明决策单调性)
即f[j2]+(s[t]-s[j2]-L)^2 < f[j1]+(s[t]-s[j1]-L)^2
容易理解s[t]=s[i]+v
所以得到(1)不等式:f[j2]+(s[i]-s[j2]-L+v)^2<f[j1]+(s[i]-s[j1]-L+v)^2
因为已知(2)不等式:f[j2]+(s[i]-s[j2]-L)^2<f[j1]+(s[i]-s[j1]-L)^2
所以化简(1)不等式:把s[i]-s[j2]-L看成一个整体,v看成一个整体,得到:
f[j2]+(s[i]-s[j2]-L)^2+2*v*(s[i]-s[j2]-L)+v^2 <f[j1]+(s[i]-s[j1]-L)^2+2*v*(s[i]-s[j1]-L)+v^2
比较(2)不等式:
左边多了一部分:2*v*(s[i]-s[j2]-L)+v^2
右边多了一部分:2*v*(s[i]-s[j1]-L)+v^2
所以我们只需要证:
2*v*(s[i]-s[j2]-L)+v^2<=2*v*(s[i]-s[j1]-L)+v^2
即:(s[i]-s[j2]-L)<=(s[i]-s[j1]-L)
即: -s[j2] <= -s[j1]
即:s[j1]<s[j2]这是肯定的,所以得证。
总结:对于当前i:j2比j1好,那么对于t(i<t)来说一样:j2一样比j1好,
所以当前i选择j2,淘汰j1,以后的t也不会在j2存在的时候选择j1,
所以i的时候就可以永久淘汰j1.
然后来求斜率方程
因为f[j2]+(s[i]-s[j2]-L)^2<=f[j1]+(s[i]-s[j1]-L)^2
展开:
f[j2]+(s[i]-L)^2-2*(s[i]-L)*s[j2]+s[j2]^2<=f[j1]+(s[i]-L)^2-2*(s[i]-L)*s[j1]+s[j1]^2
即f[j2]-2*(s[i]-L)*s[j2]+s[j2]^2<=f[j1]-2*(s[i]-L)*s[j1]+s[j1]^2
即f[j2]+s[j2]^2-2*(s[i]-L)*s[j2]<=f[j1]+s[j1]^2-2*(s[i]-L)*s[j1]
即[(f[j2]+s[j2]^2)-(f[j1]+s[j1]^2)]<=2*(s[i]-L)*s[j2]-2*(s[i]-L)*s[j1]
即[(f[j2]+s[j2]^2)-(f[j1]+s[j1]^2)]/(s[j2]-s[j1])<=2*(s[i]-L)
对于j来说:
制造的点坐标
Y=f[j]+s[j]^2
X=s[j]
我们用队列list在存有意义的决策点,list中相邻两点的斜率递增(队列中的点形成一个下凸壳),而且都大于2*(s[i]-L),那么队列头对于i来说就是最优决策点。
加入决策i时,令队尾为list[tail],前一个为list[tail-1]
斜率函数slop(点1,点2)
满足:slop(list[tail-1],list[tail])>slop(list[tail],i)时,
那么队尾list[tail]在三者(list[tail-1],list[tail],i)对于未来的tail绝对不会是最优的策略,所以将其弹出tail--
最后遇到了:slop(list[tail-1],list[tail])<slop(list[tail],i),保证了队列的相邻两点的斜率递增所以加入i:list[++tail]=i;
然后f[n]就是答案了
参考代码:
#include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> using namespace std; typedef long long LL; LL a[51000],s[51000],f[51000]; /* f[i]=min(f[j]+(sum[i]-sum[j]+i-j-1-L)^2) f[i]=min(f[j]+(s[i]-s[j]-L)^2) f[j2]+(s[i]-s[j2]-L)^2<=f[j1]+(s[i]-s[j1]-L)^2 f[j2]+(s[i]-L)^2-2*s[j2]*(s[i]-L)+s[j2]^2<=f[j1]+(s[i]-L)^2-2*s[j1]*(s[i]-L)+s[j1]^2 f[j2]+s[j2]^2-2*s[j2]*(s[i]-L)<=f[j1]+s[j1]^2-2*s[j1]*(s[i]-L) f[j2]-f[j1]+s[j2]^2-s[j1]^2<=2*s[j2]*(s[i]-L)-2*s[j1]*(s[i]-L) f[j2]-f[j1]+s[j2]^2-s[j1]^2<=2*s[j2]*(s[i]-L)-2*s[j1]*(s[i]-L) (f[j2]-f[j1]+s[j2]^2-s[j1]^2)/(s[j2]-s[j1])<=2*(s[i]-L) */ double slop(int j1,int j2) { return (f[j2]-f[j1]+s[j2]*s[j2]-s[j1]*s[j1])/(s[j2]-s[j1]); } int list[51000];int head,tail; int main() { LL L;int n; scanf("%d%lld",&n,&L);L++; for(int i=1;i<=n;i++) scanf("%lld",&a[i]); s[1]=a[1]+1; for(int i=2;i<=n;i++) s[i]=s[i-1]+a[i]+1; head=1;tail=1;list[1]=0; for(int i=1;i<=n;i++) { while(head<tail&&slop(list[head],list[head+1])<=2.0*(s[i]-L)) head++; int j=list[head]; f[i]=f[j]+(s[i]-s[j]-L)*(s[i]-s[j]-L); while(head<tail&&slop(list[tail],i)<slop(list[tail-1],list[tail])) tail--; list[++tail]=i; } printf("%lld\n",f[n]); return 0; }