首页 > 代码库 > BZOJ1911 [Apio2010]特别行动队 - 动态规划 - 斜率优化
BZOJ1911 [Apio2010]特别行动队 - 动态规划 - 斜率优化
欢迎访问~原文出处——博客园-zhouzhendong&AK
去博客园看该题解
题目传送门
题意概括
把一个整数序列划分成任意连续的段,使得划分出来的每一段的价值和最大。
对于某一段,价值的计算公式为 V=ax^2+bx+c,其中 x 为当前段的数值和。
题解
这题是博主大蒟蒻的第一道斜率优化DP题……
C++:while (1) 懵逼++;
Pascal:while (true) do inc(懵逼);
本题首先一看就是 DP 题。
但是一看 1<=n<=1000000,-5<=a<=-1,|b|<=10000000,|c|<=10000000,1<=xi<=100
彻底吓懵!
一脸懵逼……
还是一脸懵逼……
突然发现可以用斜率优化。
为了减少代码量,避免 coder 写两份代码,好心的出题人特意规定了-5<=a<=-1!
我们来考虑一下:
用 dp[i] 表示划分到前 i 个所能得到的最大价值和。
我们设 sum[i] 为前 i 个的前缀和
那么 dp[i]=max(dp[j]+a*(sum[i]-sum[j])^2+b*(sum[i]-sum[j])+c) (0<=j<i)
貌似是一个 n^2 的状态转移方程。
其实就是一个 n^2 的状态转移方程。
接下来就是斜率优化了!
我们假设对于 dp[i] 来说,从 j 转移比从 k 转移更优秀(j > k),那么有如下的表达式:
dp[j] + a*(sum[i]-sum[j])^2 + b*(sum[i]-sum[j]) + c > dp[k] + a*(sum[i]-sum[k])^2 + b*(sum[i]-sum[k]) + c
so
dp[j] + a*(sum[i]-sum[j])^2 + b*(sum[i]-sum[j]) > dp[k] + a*(sum[i]-sum[k])^2 + b*(sum[i]-sum[k])
dp[j] + a*sum[i]^2 - 2*a*sum[i]*sum[j] + a*sum[j]^2 + b*sum[i] - b*sum[j] > dp[k] + a*sum[i]^2 - 2*a*sum[i]*sum[k] + a*sum[k]^2 + b*sum[i] - b*sum[k]
dp[j] - 2*a*sum[i]*sum[j] + a*sum[j]^2 - b*sum[j] > dp[k] - 2*a*sum[i]*sum[k] + a*sum[k]^2 - b*sum[k]
(dp[j] + a*sum[j]^2 - b*sum[j]) - (dp[k] + a*sum[k]^2 - b*sum[k]) > 2*a*sum[i]*(sum[j] - sum[k])
so
((dp[j] + a*sum[j]^2 - b*sum[j])-(dp[k] + a*sum[k]^2 - b*sum[k])) / (sum[j] - sum[k]) > 2*a*sum[i]
我们设 x[p] = sum[p] , y[p] = dp[p] + a*sum[p]^2 - b*sum[p],
那么原来的方程可以表示为:
(y[j]-y[k]) / (x[j]-x[k]) > 2*a*sum[i]
左边不就是斜率的表达式吗!!
所以叫斜率优化。
Oh~ so_ga!
当然前面的不懂脑筋都可以搞出来,关键是接下来的:
我们设g(i,j) = (y[i]-y[j]) / (x[i]-x[j])
注意 a 是一个负数,而且 sum[i] 是随着i的增大而增大的,所以 2*a*sum[i] 一定是单调递减的!
如果 g(i,j)>g(j,k) 那么决策 j 一定是没用的!(k<j<i)
分两种情况进行讨论:
1. 如果g(i,j)>2*a*sum[x],那么说明决策 i 优于决策 j ,那么 j 就是没用的。就算以后 2*a*sum[x] 会变,x 只能变得,所以 2*a*sum[x] 也只能变小,所以该表达式仍然满足。
2. 如果g(i,j)<2*a*sum[x],那么g(j,k)<2*a*sum[x],那么 j 就会比 k 劣,同样也会把 j 扔掉。
用上以上的分析仍然不够。我们还需要一个合理的高(keng)级(bi)数据结构,单(keng)调(bi)队列!
在 dp 的过程中,按照“如果 g(i,j)>g(j,k) 那么决策 j 一定是没用的!”的规则入队,按照如果 g(i,j)>2*a*sum[x] 的规则出队即可。
代码
#include <cstdio> #include <algorithm> #include <cstdlib> #include <cstring> #include <cmath> using namespace std; typedef long long LL; const int N=1000000+5; int n,head,tail; LL a,b,c,r[N],sum[N],x[N],y[N],dp[N],q[N]; double g(int i,int j){ double xi=x[i],xj=x[j],yi=y[i],yj=y[j]; return (yi-yj)/(xi-xj); } int main(){ scanf("%d%lld%lld%lld",&n,&a,&b,&c); sum[0]=0; for (int i=1;i<=n;i++) scanf("%lld",&r[i]),sum[i]=sum[i-1]+r[i]; memset(x,0,sizeof x); memset(y,0,sizeof y); head=1,tail=0; q[++tail]=0; for (int i=1;i<=n;i++){ while (head+1<=tail&&g(q[head],q[head+1])>2*a*sum[i]) head++; LL s=sum[i]-sum[q[head]]; dp[i]=dp[q[head]]+a*s*s+b*s+c; x[i]=sum[i],y[i]=dp[i]+a*sum[i]*sum[i]-b*sum[i]; while (head+1<=tail&&g(q[tail-1],q[tail])<g(q[tail],i)) tail--; q[++tail]=i; } printf("%lld",dp[n]); return 0; }
BZOJ1911 [Apio2010]特别行动队 - 动态规划 - 斜率优化