【斜率优化】玩具装箱
题意:
有n 个玩具需要装箱,每个玩具的长度为c[i],规定在装箱的时候,必须严格按照给出的顺序进行,并且同一个箱子中任意两个玩具之间必须且只能间隔一个单位长度,换句话说,如果要在一个箱子中装编号为i~j 的玩具,则箱子的长度必须且只能是l=j-i+sigma[c[k]],规定每一个长度为 l 的箱子的费用是P = (l - L)2,其中 L 是给定的一个常数。现在要求你使用最少的代价将所有玩具装箱,箱子的个数无关紧要.
思路:
dp[i]=min(dp[j]+(sum[i]-sum[j]+i-j-1-L)^2) (j<i)
令f[i]=sum[i]+i,c=1+l
则dp[i]=min(dp[j]+(f[i]-f[j]-c)^2)
1.证明决策单调性
设在状态i处的k决策优与j决策,即
dp[k]+(f[i]-f[k]-c)^2<=dp[j]+(f[i]-dp[j]-c)^2
对于后的某状态t,dp[t]=dp[i]+v;
要证明dp[k]+(f[t]-f[k]-c)^2<=dp[j]+(f[t]-f[j]-c)^2
只要证
dp[k]+(f[i]+v-f[k]-c)^2<=dp[j]+(f[i]+v-f[j]-c)^2
只要证
dp[k]+(f[i]-f[k]-c)^2+2*v*(f[i]-f[k]-c)+v^2<=dp[j]+(f[i]-f[j]-c)^2+2*v*(f[i]-f[j]-c)+v^2
只要证
2*v*(f[i]-f[k]-c)<=2*v*(f[i]-f[j]-c)
即f[k]>=f[j](显然)
证明完毕
2.求斜率方程
因为dp[k]+(f[i]-f[k]-c)^2<=dp[j]+(f[i]-f[j]-c)^2
展开
dp[k]+f[i]^2-2*f[i]*(f[k]+c)+(f[k]+c)^2<=dp[j]+f[i]^2-2*f[i]*(f[j]+c)+(f[j]+c)^2
即
dp[k]-2*f[i]*(f[k]+c)+(f[k]+c)^2<=dp[j]-2*f[i]*(f[j]+c)+(f[j]+c)^2
即(dp[k]+(f[k]+c)^2-dp[j]-(f[j]+c)^2)/2*(f[k]-f[j])<=f[i]
f[i]是单调递增的,我们使用队列维护一个下凸壳,每次取出队头作为决策
加入决策i时,令队尾为q[r],前一个为q[r-1]
满足斜率(q[r],i)<斜率(q[r-1],q[r])时,显然队尾是无效的,将其弹出(转自)
代码:
1 #include<map> 2 #include<set> 3 #include<ctime> 4 #include<cstdio> 5 #include<cstring> 6 #include<vector> 7 #include<cstdlib> 8 #include<iostream> 9 #include<algorithm> 10 #define inf 1000000000 11 #define ll long long 12 using namespace std; 13 ll read() 14 { 15 ll x=0,f=1;char ch=getchar(); 16 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 17 while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} 18 return x*f; 19 } 20 int n,L,l,r; 21 int c[50005],q[50005]; 22 ll s[50005],f[50005],C; 23 double slop(int j,int k) 24 { 25 return (f[k]-f[j]+(s[k]+C)*(s[k]+C)-(s[j]+C)*(s[j]+C))/(2.0*(s[k]-s[j])); 26 } 27 void dp() 28 { 29 l=1;r=0;q[++r]=0; 30 for(int i=1;i<=n;i++) 31 { 32 while(l<r&&slop(q[l],q[l+1])<=s[i])l++; 33 int t=q[l]; 34 f[i]=f[t]+(s[i]-s[t]-C)*(s[i]-s[t]-C); 35 while(l<r&&slop(q[r],i)<slop(q[r-1],q[r]))r--; 36 q[++r]=i; 37 } 38 } 39 int main() 40 { 41 n=read();L=read();C=L+1; 42 for(int i=1;i<=n;i++)c[i]=read(); 43 for(int i=1;i<=n;i++)s[i]=s[i-1]+c[i]; 44 for(int i=1;i<=n;i++)s[i]+=i; 45 dp(); 46 printf("%lld\n",f[n]); 47 return 0; 48 }