斜率优化dp小结

来源:互联网 发布:sql注入攻击登录 编辑:程序博客网 时间:2024/06/05 16:15

先推荐一篇博客
下文有小部分修改自:http://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html
有些DP方程可以转化成DP[i]=f(i,j)+x[i]的形式,其中f[j]与i和j有关。这样的DP方程无法直接使用单调队列进行优化,所以考虑另外一中降低复杂度的方式:斜率优化!
举个例题:hdu 3507
设dp[i]表示到i的最少花费,sum[i]表示从a[1]到a[i]的数字和,有dp[i]=dp[j]+(sum[i]-sum[j])^2+M。假设k < j < i。如果在j的时候决策要比在k的时候决策好,即
dp[j]+M+(sum[i]-sum[j])^2 < dp[k]+M+(sum[i]-sum[k])^2。(求最小花费,所以优就是小于)
上不等式可以表示成yj-yk/xj-xk < sum[i],左边就是斜率g(j,k)的表示。
以下是核心操作:
假设k < j < i并且g(i,j) < g(j,k)那么j一定不属于此题(优表示小于的情况)最优解集。
①若g(i,j) < sum[p],那么显然i比j优,排除j
②若g(i,j) ≥ sum[p],那么j比i优,但是有一前提成立g(j,k) > g(i,j),所以g(j,k) > sum[p],所以k比j优,j也可以排除。
于是成功地排除了无效点,维护了一个下凸的图形
下图中左边为有效点集,右边为存在无效点的集合
这里写图片描述
具体代码实现:
定义一个单调队列。
①移动队首,找到一个斜率最大的合法的g(h+1,h),即右移到最后一个g(h+1,h)。
②用队首更新当前点(dp数组赋值)
③不断左移队尾,剔除不合法的点集。

下面是hdu 3507的AC代码:

/*    dp[i]=dp[j]+M+(sum[i]-sum[j])^2    (dp[j]+sum[j]^2-(dp[k]+sum[k]^2))/(2*(sum[j]-sum[k]))<sum[i]*/#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;const int maxn=500002;int n,M;int sum[maxn],dp[maxn],q[maxn];inline int getY(int x,int y) {    return dp[x]+sum[x]*sum[x]-dp[y]-sum[y]*sum[y];}inline int getX(int x,int y) {    return (sum[x]-sum[y])<<1;}int main() {    while (~scanf("%d%d",&n,&M)) {        int h=0,t=0;        sum[0]=dp[0]=0;        for (int i=1;i<=n;++i) {            scanf("%d",&sum[i]);            sum[i]+=sum[i-1];            while (h<t&&getY(q[h+1],q[h])<=getX(q[h+1],q[h])*sum[i]) ++h;            dp[i]=dp[q[h]]+M+(sum[i]-sum[q[h]])*(sum[i]-sum[q[h]]);            while (h<t&&getY(i,q[t])*getX(q[t],q[t-1])<=getY(q[t],q[t-1])*getX(i,q[t])) --t;            q[++t]=i;        }        printf("%d\n",dp[n]);    }    return 0;}