斜率dp题


hdu 3507 Print Article

题意:

给长度为n的数组a,要求切割成若干个连续区间:
切割完之后每个块的成本是:(区间和)2+M
问切割的最小成本是多少

思路:

学自bin巨:
d[i]表示前i个的最小费用,转移方程为d[i]=min{d[j]+(sum[i]-sum[j])^2+M},(j<i)
复杂度是O(n^2),但是n<=2e5,复杂度不满足要求,还需要优化

假设在计算d[i]的时候,k<j且k不如j优,则:
d[j]+(sum[i]-sum[j])^2+M<d[k]+(sum[i]-sum[k])^2+M,移项+化简可得:
[(d[j]+sum[j]^2)-(d[k]+sum[k]^2)]/(2sum[j]-2sum[k])<sum[i]

令y[j]=d[j]+sum[j]^2,x[j]=2sum[j],则式子变为:
(y[j]-y[k])/(x[j]-x[k])<sum[i],发现这是斜率式,且右边的sum[i]是递增的

令g[j,k]=(y[j]-y[k])/(x[j]-x[k])
1.去掉劣解:如果满足j比k优,即g[j,k]<sum[i],因为右边sum[i]是递增的,所以对于i后面的i+1等
都满足这个式子,也就是说j一直都比k优,那么k可以被淘汰
2.准备插入i:假设i>j>k,g[i,j]<g[j,k],那么j可以被淘汰
如果g[i,j]<sum[i],即i比j优,j可以被淘汰,
如果g[i,j]>sum[i],因为g[i,j]<g[j,k],所以有g[j,k]>sum[i],那么k比j优,j可以被淘汰
(注意不等式符号方向)

用队列维护,具体操作方法见代码

code:

#include<bits/stdc++.h>
using namespace std;
const int maxm=5e5+5;
int q[maxm],head,tail;
int sum[maxm];
int d[maxm];
int n,m;
int dp(int i,int j){
    return d[j]+(sum[i]-sum[j])*(sum[i]-sum[j])+m;
}
int up(int j,int k){//y[j]-y[k]
    return (d[j]+sum[j]*sum[j])-(d[k]+sum[k]*sum[k]);
}
int down(int j,int k){//x[j]-x[k]
    return 2*(sum[j]-sum[k]);
}
signed main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        for(int i=1;i<=n;i++){
            scanf("%d",&sum[i]);
            sum[i]+=sum[i-1];
        }
        head=tail=0;//head=tail的时候表示队列为空
        q[tail++]=0;//d[i]可能从d[0]转移,因此插入一个0
        for(int i=1;i<=n;i++){
            //不等式的相除转化为相乘,不过不转可能也不会wa
            //head+1<tail说明队内至少有两个元素
            while(head+1<tail){//去掉劣解;
                int j=q[head+1],k=q[head];
                int x=up(j,k);
                int y=sum[i]*down(j,k);
                if(x<=y)head++;//这时j比k优,因此去掉k
                else break;
            }
            d[i]=dp(i,q[head]);//现在队头就是最优的j
            while(head+1<tail){//准备插入i;
                int j=q[tail-1],k=q[tail-2];
                int x=up(i,j)*down(j,k);
                int y=up(j,k)*down(i,j);
                if(x<=y)tail--;//g[i,j]<=g[j,k]
                else break;
            }
            q[tail++]=i;
        }
        printf("%d\n",d[n]);
    }
    return 0;
}

bzoj1096 [ZJOI2007]仓库建设

题意:

在这里插入图片描述

思路:

d[i]为在i位置建立仓库的最小代价
d[i]=min{d[j]+cal(j,i)}+c[i]},其中cal(j,i)是把(j+1)到i的所有物品存到i的花费
cal(j,i)=x[i]*sigma(p[k])-sigma(x[k]*p[k]),其中k的取值为j+1<=k<=i
sigma(p[k])sigma(x[k]*p[k])可以用前缀和预处理
设s[]=p[]的前缀和,ss[]为x[]*p[]的前缀和
那么cal(j,i)=x[i]*(s[i]-s[j])-(ss[i]-ss[j])
那么总式子就是 d[i]=min{d[j]+x[i]*(s[i]-s[j])-(ss[i]-ss[j])}

现在假设j优于k,那么:
d[j]+x[i]*(s[i]-s[j])-(ss[i]-ss[j])<d[k]+x[i]*(s[i]-s[k])-(ss[i]-ss[k])
化简移项一下变为:
[(d[j]+ss[j])-(d[k]+ss[k])]/(s[j]-s[k])<x[i]
令y[j]=d[j]+ss[j],x[j]=s[j]
则式子变为(y[j]-y[k])/(x[j]-x[k])< x[i],是一个斜率式
斜率优化一下就行了

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e6+5;
int x[maxm],p[maxm],c[maxm];
int q[maxm],head,tail;
int s[maxm],ss[maxm];
int d[maxm];
int n;
int dp(int i,int j){
    return d[j]+x[i]*(s[i]-s[j])-(ss[i]-ss[j])+c[i];
}
int up(int j,int k){
    return (d[j]+ss[j])-(d[k]+ss[k]);
}
int down(int j,int k){
    return s[j]-s[k];
}
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>x[i]>>p[i]>>c[i];
    }
    for(int i=1;i<=n;i++){
        s[i]=s[i-1]+p[i];
        ss[i]=ss[i-1]+x[i]*p[i];
    }
    head=tail=0;
    q[tail++]=0;
    for(int i=1;i<=n;i++){
        while(head+1<tail){
            int j=q[head+1],k=q[head];
            int l=up(j,k);
            int r=x[i]*down(j,k);
            if(l<=r)head++;
            else break;
        }
        d[i]=dp(i,q[head]);
        while(head+1<tail){
            int j=q[tail-1],k=q[tail-2];
            int l=up(i,j)*down(j,k);
            int r=up(j,k)*down(i,j);
            if(l<=r)tail--;
            else break;
        }
        q[tail++]=i;
    }
    cout<<d[n]<<endl;
    return 0;
}

bzoj1010 [HNOI2008]玩具装箱toy

题意:

在这里插入图片描述
n<=5e4,L,c(i)<=1e7

思路:

d[i]=min{d[j]+cal(j,i)}
cal(j,i)=[i-(j+1)+sigma(c[k])-L]^2  ,其中j+1<=k<=i
设s[]为c[]的前缀和,再改变一下项的位置,则式子变为:
d[i]=min{d[j]+[(s[i]+i)-(s[j]+j+1+L)]^2};

令a[i]=s[i]+i,b[i]=s[i]+i+1+L,缩项之后式子为:
d[i]=min{d[j]+(a[i]-b[j])^2}

假设j比k优,则有式子:
d[j]+(a[i]-b[j])^2<d[k]+(a[i]-b[k])^2,移项化简一下得:
[(d[j]+b[j]^2)-(d[k]+b[k]^2)]/(2*b[j]-2*b[k])<a[i]
预处理s[],a[],b[]即可

总结:推式子的时候可以缩项,更容易接下来的推导

ps:
这题要注意边界的初始化,b[0]=s[0]+0+1+L,即b[0]=L+1

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=5e4+5;
int s[maxm],a[maxm],b[maxm];
int q[maxm],head,tail;
int d[maxm];
int n,L;
int dp(int i,int j){
    return d[j]+(a[i]-b[j])*(a[i]-b[j]);
}
int up(int j,int k){
    return (d[j]+b[j]*b[j])-(d[k]+b[k]*b[k]);
}
int down(int j,int k){
    return 2*(b[j]-b[k]);
}
signed main(){
    cin>>n>>L;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        s[i]+=s[i-1];
        a[i]=s[i]+i;
        b[i]=s[i]+i+1+L;
    }
    b[0]=L+1;
    head=tail=0;
    q[tail++]=0;
    for(int i=1;i<=n;i++){
        while(head+1<tail){
            int j=q[head+1],k=q[head];
            int x=up(j,k);
            int y=a[i]*down(j,k);
            if(x<=y)head++;
            else break;
        }
        d[i]=dp(i,q[head]);
        while(head+1<tail){
            int j=q[tail-1],k=q[tail-2];
            int x=up(i,j)*down(j,k);
            int y=up(j,k)*down(i,j);
            if(x<=y)tail--;
            else break;
        }
        q[tail++]=i;
    }
    cout<<d[n]<<endl;
    return 0;
}

其他:

网上看到的技巧:
dp[i]=a[i]+b[j],O(n^2),用单调队列可以优化到O(n)
dp[i]=a[i]*b[j]+c[i]+d[j],因为有a[i]*b[j],因此单调队列不行了,用斜率优化

发布了431 篇原创文章 · 获赞 36 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_44178736/article/details/105040953