斜率优化dp小结

先推荐一篇博客
下文有小部分修改自: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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值