斜率DP优化

来源:互联网 发布:游戏编程 知乎 编辑:程序博客网 时间:2024/06/01 09:25

因为NOIP到来开始狂补算法,不知道要不要考斜率DP优化。
PS:以下出现sum表示前缀和


斜率优化主要优化与线性DP,一般的线性DP,转移方程:
f[i]=min(f[j]+sum[i]sum[j])
f[i]=min(f[j]+a[j]+a[i])
这类线性DPi和j都可以分开计算,所以直接使用线段树,单调栈/单调队列,堆等数据结构快速优化,但是也有这么一类转移方程,i和j都有相乘这类关系,如:
f[i]=min(f[j]+(sum[i]sum[j])2)+M)
这种方程就不能用普通的方法优化了,需要用到斜率优化。
PS:假设这里的数都为正数,即sum数组递增


如果对于i转移的时候,有j比k更优k<j<i,则

f[j]+(sum[i]sum[j])2+M<f[k]+(sum[i]sum[k])2+M
f[j]+sum[j]22sum[i]sum[j]<f[k]+sum[k]22sum[i]sum[k]
f[j]+sum[j]2(f[k]+sum[k]2)<2sum[i](sum[j]sum[k])
f[j]+sum[j]2(f[k]+sum[k]2)2sum[j]2sum[k]<sum[i]

X(i)=2sum[i],Y(i)=f[i]+sum[i]2,则

Y(j)Y(k)X(j)X(k)<sum[i]

所以……这就是斜率呀。

又令K(i,j)=Y(j)Y(i)X(j)X(i),则K(k,j)<sum[i]

所以对于i来说j比k优秀,那么我们可以在求f[i]的时候干掉k了,因为k不会再次优秀。

接下来还可以推一个结论 K(k,j)K(j,i)→ j 不会或没必要出现在最优解中,为什么呢?

1.K(j,i)<sum[i] ,那么对 i 来说, i 比 j 优秀,由于sum递增,所以 i 将一直比 j 优秀。
2.K(j,i)sum[i] ,那么 K(k,j)≥K(j,i)≥sum[i] ,即对 i 来说, k 比 j 优秀,由于sum递增,所以 k 将一直比 j 优秀。

所以队列中的候选最优解一定满足 K(que[i1],que[i])<K(que[i],que[i+1])(hed<i<ti) ,即斜率递增。


所以最后到底怎么做:
1.判断K(que[hed],que[hed+1])<sum[i],有的话hed++;
2.que[hed]是最优政策,求f[i]
3.判断K(que[til1],que[til])>=K(que[til],i),有的话til–;
4.que[++til]=i;
真的有这道题,HDU 3507 HDU貌似挂了,挂个vjudge的链接

#include<cstdio>#include<cstring>#define LL long longusing namespace std;int n,m,que[500005];LL sum[500005],f[500005];inline void readi(int &x){    x=0; char ch=getchar();    while ('0'>ch||ch>'9') ch=getchar();    while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}}LL getX(const int i,const int j){return 2*sum[j]-2*sum[i];}LL getY(const int i,const int j){return f[j]+sum[j]*sum[j]-f[i]-sum[i]*sum[i];}void _work(){    int hed=1,til=1;    memset(f,63,sizeof(f)); f[0]=sum[0]=que[1]=0;    for (int i=1,x;i<=n;i++){        readi(x); sum[i]=sum[i-1]+x;        while (hed<til&&getY(que[hed],que[hed+1])<getX(que[hed],que[hed+1])*sum[i]) hed++;        f[i]=f[que[hed]]+(sum[i]-sum[que[hed]])*(sum[i]-sum[que[hed]])+m;        while (hed<til&&getY(que[til-1],que[til])*getX(que[til],i)>=getX(que[til-1],que[til])*getY(que[til],i)) til--;        que[++til]=i;    }    printf("%lld\n",f[n]);}int main(){    freopen("printer.in","r",stdin);    freopen("printer.out","w",stdout);    while (scanf("%d%d",&n,&m)==2) _work();    return 0;}

注意K(k,j)<sum[i],其中右边的sum[i]这个值是保证递增的,这样才可以直接用单调序列,如果不递增……
二分?splay?cdq分治?反正都不会用

原创粉丝点击