斜率DP小结

此文大部分来自以下博客
如果有需要学习请移步以下链接
【笔记】【总结】斜率DP及习题-Little_Fall
斜率优化-南枙向暖

此文主要初学的我为了加深自己印象,难免错误,请大家勿往下阅读

引入
当我们在遇到这样的DP方程时 d p [ i ] = d p [ j ] + ( x [ i ] − x [ j ] ) ∗ ( x [ i ] − x [ j ] ) dp[i]=dp[j]+(x[i]-x[j])*(x[i]-x[j]) dp[i]=dp[j]+(x[i]x[j])(x[i]x[j]),如果把右边的乘法化开的话,会有 x [ i ] ∗ x [ j ] x[i]*x[j] x[i]x[j]的项,它不能分解为只与i或j有关的部分。这里学习一种新的优化方法,叫做斜率优化。

例题: HDU3507 Print Article
给定n(5e5)和m(1000),以及一个长为n的数列 a i a_i ai,现在要把数列分成若干个连续段。定义每个连续段的代价是 ∑ a i 2 + m \sum{a_i}^2+m ai2+m,求划分后的最小代价。

思路:
dp[i]: 表示把前i个数划分完的最小代价
sum[i]: 从 a 1 + … … + a i a_1+……+a_i a1++ai的和
则我们可以得到初始式子 d p [ i ] = m i n ( d p [ j ] + ( s u m [ i ] − s u m [ j ] ) 2 ) , ( 0 < = j < i , d p [ 0 ] = 0 ) dp[i]=min(dp[j]+(sum[i]-sum[j])^2),(0<=j<i,dp[0]=0) dp[i]=min(dp[j]+(sum[i]sum[j])2),(0<=j<i,dp[0]=0)
然后我们通过变形,设j<k<i,且决策点k更优,则有
d p [ k ] + ( s u m [ i ] − s u m [ k ] ) 2 < d p [ j ] + ( s u m [ i ] − s u m [ j ] ) 2 dp[k]+(sum[i]-sum[k])^2<dp[j]+(sum[i]-sum[j])^2 dp[k]+(sum[i]sum[k])2<dp[j]+(sum[i]sum[j])2
转化得, d p [ k ] − s u m [ k ] 2 − ( d p [ j ] − s u m [ j ] 2 ) s u m [ k ] − s u m [ j ] < 2 ∗ s u m [ i ] \frac{dp[k]-sum[k]^2-(dp[j]-sum[j]^2)}{sum[k]-sum[j]}<2*sum[i] sum[k]sum[j]dp[k]sum[k]2(dp[j]sum[j]2)<2sum[i]

y j = d p [ j ] − s u m [ j ] 2 , x j = s u m [ j ] y_j=dp[j]-sum[j]^2,x_j=sum[j] yj=dp[j]sum[j]2,xj=sum[j],则 y k − y j ( x k − x j ) < 2 ∗ s u m [ i ] \frac{y_k-y_j}{(x_k-x_j)}<2*sum[i] (xkxj)ykyj<2sum[i],

未完待续

斜率DP算法流程总结

1.找到dp的转移式,通过斜率分析得到点的表示 ( x i , y i ) (x_i,y_i) (xi,yi)及目标斜率的表示
2.用双端队列维护一个下凸包,每当新来一个点时,末尾不断出队直到构不成上凸包。
3.选择最优解,即 k j , j + 1 k_{j,j+1} kj,j+1大于目标斜率的第一个j。当目标斜率递增时,每次更新队首,然后可以选择队首作为最优解。
4.斜率少用double,容易有误差

#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<stack>
#include<queue>
#define INF 0x3f3f3f3f
#define lowbit(x) x & -x
#define lson root<<1,l,mid
#define rson root<<1|1,mid+1,r
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=5e5+5;

int n,m,head,tail;
int dp[N],sum[N],q[N];

int getdp(int i,int j){
    return dp[j]+m+(sum[i]-sum[j])*(sum[i]-sum[j]);
}

int getup(int j,int k){
    return dp[j]+sum[j]*sum[j]-(dp[k]+sum[k]*sum[k]);
}

int getdown(int j,int k){
    return 2*(sum[j]-sum[k]);
}

int main(){
#ifdef Mizp
    freopen("in.txt","r",stdin);
#endif
    while(scanf("%d%d",&n,&m)!=EOF){
        sum[0]=dp[0]=0;
        for(int i=1;i<=n;i++){
            scanf("%d",&sum[i]);
            sum[i]+=sum[i-1];
        }
        head=tail=0;
        q[tail++]=0;
        for(int i=1;i<=n;i++){
            while(head+1<tail && getup(q[head+1],q[head])<=sum[i]*getdown(q[head+1],q[head]))
                head++;
            dp[i]=getdp(i,q[head]);
            while(head+1<tail && getup(i,q[tail-1])*getdown(q[tail-1],q[tail-2])<=getup(q[tail-1],q[tail-2])*getdown(i,q[tail-1]))
                tail--;
            q[tail++]=i;
        }
        printf("%d\n",dp[n]);
    }
    return 0;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值