【hdu3507】Print Article
今天重点想学习一些dp的优化技巧,比如斜率优化
这个题算是一个斜率优化的一个板子题,给你一串数字以及一个常数m,让你求所有区间其和的平方加常数的和的最小值
听起来有点麻烦,仔细看题还是觉得可以接受的
n^2的状态转移方程很好想 dp[i]=min(dp[i],dp[j]+(sum[i]-sum[j])^2)
然鹅数据最大能有500000个数字,n^2肯定过不了,因此要考虑优化。事实上,通过斜率优化,可以将时间复杂度降到n
假设k<j<i,并且从j转移要比从k转移更优
所以dp[j]+(sum[i]-sum[j])^2+m<dp[k]+(sum[i]-sum[k])^2+m
通过一系列的化简和移项可以得到(dp[j]+sum[j]^2-dp[k]-sum[k]^2)/2(sum[j]-sum[k])<sum[i]
我们设dp[j]+sum[j]^2=yj,dp[k]+sum[k]^2=yk,2*sum[j]=xj,2*sum[k]=xk
所以(yj-yk)/(xj-xk)<sum[i]
我们不难看出,左侧式子的计算结果应是斜率
所以最关键的地方了,设g(i,j)是我们所求的从i到j的那个斜率,如果g(i,j)<g(j,k),那么j就不用放在状态队列进行转移了
分类讨论一下,如果g(i,j)<sum[i],那么i要比j更优,所以j会被踢出队列
如果g(i,j)>=sum[i],那么g(j,k)>g(i,j)>=sum[i],k要比i和j两个都优,所以j也会被踢出
因此斜率是单调递减的
因此用个单调队列来维护需要转移的状态,在进行转移的时候,我们需要把不满足条件的队首全部踢出去,把i加入队列的时候,把不优的队尾踢出
#include<iostream> #include<cstring> #include<cstdio> using namespace std; int n,m,head,tail,dp[500050],q[500050],s[500050]; inline int fz(int j,int k)//求那串化简的式子过后的分子 { return dp[j]+s[j]*s[j]-dp[k]-s[k]*s[k]; } inline int fm(int j,int k)//求分母 { return 2*(s[j]-s[k]); } inline int getdp(int i,int j)//进行转移 { return dp[j]+m+(s[i]-s[j])*(s[i]-s[j]); } //用g(i,j)表示从i到j的斜率 int main() { while(~scanf("%d%d",&n,&m)) { s[0]=dp[0]=0;//初始化 for(int i=1;i<=n;i++) scanf("%d",&s[i]),s[i]+=s[i-1];//预处理前缀和 head=tail=0;//清空队列 q[tail++]=0; for(int i=1;i<=n;i++) { while(head+1<tail&&fz(q[head+1],q[head])<=s[i]*fm(q[head+1],q[head])) head++;//如果g(i,j)<g(j,k),j会被踢出状态队列 dp[i]=getdp(i,q[head]);//与队首进行转移 while(head+1<tail&&fz(i,q[tail-1])*fm(q[tail-1],q[tail-2])<=fz(q[tail-1],q[tail-2])*fm(i,q[tail-1])) tail--;//发现当前队尾不可能为需要转移的状态时,将其踢出 q[tail++]=i;//将i放进去 } printf("%d\n",dp[n]); } }