Kuangbin带你飞 专题二十 斜率DP

HDU 3507 - Print Article

题意:

一个长度为n的序列,你可以将其分成任意段,每段的花费为sum[i....j]2+M,求整个序列的最小花费

思路:

dp求解,f[i]表示前i项的最小代价,s为前缀和

转移方程f[i]=min(f[i],f[j]+(s[i]-s[j])^2+M),dp复杂度O(N^2),使用斜率优化。

设f[i]转移从j比k好(k < j < i) 得斜率方程

f[j]+(s[i]-s[j])^2 +M<f[k]+(s[i]-s[k])^2+M 约去相同项 jk项移至左测 i和常移至右侧得

((f[j] + s[j]^2) - (f[k] + s[k]^2))/(s[j]-s[k])<2s[i]

令y = f[j]+s[j]^2 ,x=s[j] 则可以表示为点(y, x)计算斜率 表示为

(yj-yk)/(xj-xk)<2s[i]为了精度改乘法(yj-yk) <2s[i]*(xj-xk) 注意符号

当斜率小于2s[i]时 可以直接淘汰j点

#include<iostream>
#include<algorithm> 
 using namespace std;
 typedef long long ll;
 const int maxn=5e5+100;
 ll dp[maxn],sum[maxn],p[1000006];
 ll pow(ll x){return x*x;}
 ll getx(int i,int j)
 {
     return dp[j]+pow(sum[j])-dp[i]-pow(sum[i]);
 }
 ll gety(int i,int j)
 {
     return 2*(sum[j]-sum[i]);
 }
 int main()
 {
     ll n,m;
     while(scanf("%lld%lld",&n,&m)!=EOF){
         for(int i=1;i<=n;i++){
             scanf("%lld",&sum[i]);
             sum[i]+=sum[i-1];
         }
        int tail=0,head=0;
        p[tail++]=0;
        for(int i=1;i<=n;i++){
            while(head+1<tail&&getx(p[head],p[head+1])<=sum[i]*gety(p[head],p[head+1])) 
                head++;
            dp[i]=dp[p[head]]+pow(sum[i]-sum[p[head]])+m;
            while(head+1<tail&&gety(p[tail-2],p[tail-1])*getx(p[tail-1],i)<=gety(p[tail-1],i)*getx(p[tail-2],p[tail-1])) 
                tail--;
            p[tail++]=i;
        }
        cout<<dp[n]<<endl;
     }
  } 
View Code

HDU 2829 - Lawrence(斜率DP)

题意:

一个长度为n的序列,进行m次分割,分割之后每段的值为该段内任意两数乘积的和,求所有段值的和的最小值

思路:

假设dp[i][j]为前i个元素进行j次分割后的最小值,cost[i]表示从1~i的数两两相乘的总和,sum[i]表示前i个数的和

则:dp[i][j]=Min(dp[k][j-1]+cost[i]-cost[k]-sum[k]*(sum[i]-sum[k]))=>dp[i][j]=dp[k][j-1]+cost[i]-cost[k]-sum[i]*sum[k]+sum[k]*sum[k]

由于有sum[i]*sum[k]这一项,所以不可能用单调队列维护-cost[k]-sum[i]*sum[k]+sum[k]*sum[k]的最小值,所以我们要把sum[i]独立出来以便求维护单调队列是和i无关 

现在我们需要找出最优的k,令k2<k时k时最优的,即前k个数k为最优的取值

所以满足:

dp[k][j-1]+cost[i]-cost[k]-sum[i]*sum[k]+sum[k]*sum[k]<=dp[k2][j-1]+cost[i]-cost[k2]-sum[i]*sum[k2]+sum[k2]*sum[k2]

=>(dp[k][j-1]-cost[k]+sum[k]*sum[k]-(dp[k2][j-1]-cost[k2]+sum[k2]*sum[k2]))/(sum[k]-sum[k2])<=sum[i]

设:y1=dp[k][j-1]-cost[k]+sum[k]*sum[k]  x1=sum[k] ,y2=dp[k2][j-1]-cost[k2]+sum[k2]*sum[k2] x2=sum[k2]

所以变成:(y2-y1)/(x2-x1),即两点之间的斜率! 之后就是经典的斜率DP问题啦,维护一个下凸包来存储最大值

#include<iostream>
#include<algorithm> 
#include<cstring>
 using namespace std;
 typedef long long ll;
 const int maxn=1005;
 ll dp[maxn][2],sum[maxn],q[10000],a[maxn],c[maxn];
 ll n,m,index;
 ll pow(ll x){return x*x;}
 
int GetY(int k,int k2){
    return dp[k][index^1]-c[k]+sum[k]*sum[k]-(dp[k2][index^1]-c[k2]+sum[k2]*sum[k2]);
}
 
int GetX(int k,int k2){
    return sum[k]-sum[k2];
}
 void DP()
 {
     index=0;
     memset(dp,0,sizeof(dp));
    for(int i=1;i<=n;i++) dp[i][index]=c[i];
    for(int j=1;j<=m;j++){
        index=index^1;
        int tail=0,head=0;
        q[tail++]=0;
        for(int i=1;i<=n;i++){
            while(head+1<tail && GetY(q[head+1],q[head])<=GetX(q[head+1],q[head])*sum[i]) 
                head++;
            dp[i][index]=dp[q[head]][index^1]+c[i]-c[q[head]]+sum[q[head]]*(-sum[i]+sum[q[head]]);
            while(head+1<tail && GetY(i,q[tail-1])*GetX(q[tail-1],q[tail-2])<=GetY(q[tail-1],q[tail-2])*GetX(i,q[tail-1])) 
                tail--;
            q[tail++]=i;
        }
    }
     
 }
 int main()
 {
     while(scanf("%lld%lld",&n,&m)&&n&&m){
         for(int i=1;i<=n;i++){
             scanf("%lld",&a[i]);
             sum[i]=sum[i-1]+a[i];
         }
        memset(c,0,sizeof(c));
        for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++)
                c[j]+=a[j]*a[i];
        for(int i=1;i<=n;i++) c[i]+=c[i-1];
        DP();
        cout<<dp[n][index]<<endl;
     }
    return 0;
  } 
View Code
原文地址:https://www.cnblogs.com/overrate-wsj/p/12585060.html