斜率DP优化

因为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 long
using 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分治?反正都不会用

猜你喜欢

转载自blog.csdn.net/try__jhf/article/details/78285005