【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]);
    }
}

 

posted @ 2017-10-25 10:13  那一抹落日的橙  阅读(329)  评论(0编辑  收藏  举报