浅谈背包dp的相关例题

前言:

DP一直是博主的一大心头痛啊,真的完全小白状态,最近刷完了至少20道的背包问题,在这篇博客中我将会将我认为有启发价值的题目列出来,中间会有筛选的(质量保证)。在复习例题前,我会先将背包的几种基本状态做个总结。请务必知道所有类型的背包后再来阅读。

一.简单背包类型总结

1.01背包

for(int i=1;i<=n;i++)
for(in j=W;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

2.完全背包

for(int i=1;i<=n;i++)
for(int j=w[i];j<=W;j++)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

3.多重背包

1)暴力拆分( O( W*sum(c[i]) ) )

for(int i=1;i<=n;i++)
for(int k=1;k<=c[i];k++)
for(int j=W;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

2)二进制拆分( O( W*sum ( log(c[i]) ) ) )

    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>cw>>cv>>c;
        int now=1;
        while(now<=c)
        {
            w[++cnt].s=cw*now;
            v[cnt].s=cv*now;
            w[cnt].id=i;
            v[cnt].id=i;
            c-=now;
            now*=2;
        }
        if(c)
        {
            w[++cnt].s=cw*c;
            w[cnt].id=i;
            v[cnt].id=i;
            v[cnt].s=cv*c;
        }
    }
    cin>>m;
    n=cnt;
    for(int i=1;i<=n;i++)
        for(in j=W;j>=w[i];j--)
        dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

3)数组优化(O(n*W))

for(int i=1;i<=n;i++)
{
    memset(num,0,sizeof(num));
    for(int j=w[i];j<=W;j++)
    {
        if(dp[j]<dp[j-w[i]] && num[j-w[i]]<c[i])
        {
            dp[j]=dp[j-w[i]]+v[i];
            num[j]=num[j-w[i]]+1;
        }
    }
}

PS:这个中间一直memset,可以换了,感觉常数有点大。

4.分组背包

for(int i=1;i<=n;i++)
for(int j=W;j>=0;j--)
for(int k=1;k<=c[i];k++)
{
    if(j>=w[i][k])
    {
        dp[j]=max(dp[j],dp[j-w[i][k]]+v[i][k]);
    }
}

5.混合背包

PS:混合背包有多种类型的组合,但原理相通。考虑到哪个物品时,就按这个物品的模式去递推一层即可。所以这里没有给出具体代码。

二.简单例题

1.1417烹饪方案

浅谈背包dp的相关例题

 

 

 我们发现因为时间的缘故导致物品的顺序对结果会有影响,所以我们先决定选的顺序。手推一下就可以发现按某种规律排列再去一个个选会有最优结果。然后就是个01DP,这里注意了因为t会对答案有影响且会有负数的存在所以中途要一直取max才行。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct node
{
    ll a,b,c;
    bool operator < (const node &tt) const 
    {
        return -tt.b*c>-b*tt.c;
    }
}a[1001];
ll dp[1000010],t,n;
int main()
{
    ll ans=0;
    cin>>t>>n;
    for(int i=1;i<=n;i++) cin>>a[i].a;
    for(int i=1;i<=n;i++) cin>>a[i].b;
    for(int i=1;i<=n;i++) cin>>a[i].c;
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++)
    for(int j=t;j>=a[i].c;j--)
    {
        dp[j]=max(dp[j],dp[j-a[i].c]+a[i].a-j*a[i].b);
        ans=max(ans,dp[j]); 
    }
    cout<<ans;
    return 0;
}

2.1855

浅谈背包dp的相关例题

 

 

 这个题倒不是多难,就是放这提一下二维背包dp

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000;
int n,m,t;
int mm[maxn],tt[maxn];
int dp[maxn][maxn];
int main()
{
    cin>>n>>m>>t;
    for(int i=1;i<=n;i++)
    cin>>mm[i]>>tt[i];
    for(int i=1;i<=n;i++)
    for(int M=m;M>=mm[i];M--)
    for(int T=t;T>=tt[i];T--)
    dp[M][T]=max(dp[M-mm[i]][T-tt[i]]+1,dp[M][T]);
    cout<<dp[m][t];
    return 0;
}

3.5020货币系统

这个题倒也不是多难,就是这个数学证明,我不会证明,但我看完题目就看出了正解(并非凡尔赛),最后答案就是这个系统中有几个数字能被系统中其他数字组合出来。

4.金明的预算方案

浅谈背包dp的相关例题

 

 

 呃....有了附件,各种乱七八糟的关系,这就很烦了。但因为附件最多只有2个,所以我们对于每个主件可以有

1.只选主件

2.一个都不选

3.选主件和附件一

4.选主件和附件二

5.都选

然后就是个01背包

5.2946

浅谈背包dp的相关例题

 

 

 这个算我见过的简单dp中有点特殊的,dp[i][j]表示考虑选了i头奶牛时,前i个组成的和modf=j的组合数。然后就是01背包。

#include<bits/stdc++.h>
using namespace std;
const int p=1e8;
int n,f;
int r[100010];
long long h[2010][1010];
int main()
{
    cin>>n>>f;
    for(int i=1;i<=n;i++)
    {
        cin>>r[i];
        r[i]%=f;
    }
    for(int i=1;i<=n;i++) h[i][r[i]]=1;
    for(int i=1;i<=n;i++)
    for(int j=0;j<f;j++)
    h[i][j]=((h[i][j]+h[i-1][j])%p+h[i-1][(j-r[i]+f)%f])%p;
    cout<<h[n][0];
    return 0;
}

6.1156垃圾陷阱

浅谈背包dp的相关例题

 

 

 呃就是dp[t][h]表示时间为t秒时,高度为h时,剩余的最大生命值。

这个题我才用的时从0开始往后推的一种dp形式,也算dp的一种技巧。

然后在dp的过程中,判断是否能逃出去。如果一轮dp下来仍然每逃出去,则必死无疑,重新求答案,这就是个简单模拟,就是不停的吃。

#include<bits/stdc++.h>
using namespace std;
int dp[101][101];
struct node
{
    int t,f,h;
    bool operator < (const node &a) const
    {
        return t<a.t;
    }
}r[100010];
int d,g;
int main()
{
    memset(dp,-1,sizeof(dp));
    cin>>d>>g;
    for(int i=1;i<=g;i++)
    cin>>r[i].t>>r[i].f>>r[i].h;
    sort(r+1,r+1+g);
    dp[0][0]=10;
    for(int i=0;i<g;i++)
    for(int j=0;j<=d;j++)
    {
        if(dp[i][j]<0) continue ;
        if(j+r[i+1].h>=d && dp[i][j]-r[i+1].t+r[i].t>=0)
        {
            cout<<r[i+1].t<<endl;
            return 0;
        }
        if(dp[i][j]-r[i+1].t+r[i].t>=0)
        {
            dp[i+1][j+r[i+1].h]=max(dp[i+1][j+r[i+1].h],dp[i][j]-r[i+1].t+r[i].t);
        }
        if(dp[i][j]-r[i+1].t+r[i].t>=0)
        dp[i+1][j]=max(dp[i+1][j],dp[i][j]-r[i+1].t+r[i].t+r[i+1].f);
    }
    int m=10,sum=0;
    for(int i=1;i<=g;i++)
    {
    if(r[i].t-r[i-1].t>m)
    {
        cout<<m+sum;
        return 0;
     } 
     sum+=(r[i].t-r[i-1].t);
     m-=(r[i].t-r[i-1].t);
     m+=r[i].f;
    }
    cout<<sum+m;
    return 0;
}

7.5322排兵布阵

浅谈背包dp的相关例题

 

 这个题题解都在说什么排序啊什么的,我表示听不懂,就是个分组背包嘛,我就拍了几分钟,一次AC。(不是在凡尔赛)但是常数好像有点大吧?.....

#include<bits/stdc++.h>
using namespace std;
int s,n,m,x;
int dp[1000001];
int c[10001][10001],t[1001][40010],sum[10000][10000];
struct
{
    int w,v;
}ttt[1010][1010];
int pp[100010];
int main()
{
    cin>>s>>n>>m;
    for(int i=1;i<=s;i++)
    {
        for(int j=1;j<=n;j++)
        {
            cin>>x;
            c[j][i]=x;
            t[j][x*2+1]++;
            pp[j]=max(pp[j],x*2+1);
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=pp[i]+2;j++)
        {
            sum[i][j]=sum[i][j-1]+t[i][j]*i;
        }
    }
    for(int i=1;i<=n;i++)
    for(int j=1;j<=s;j++)
    {
        ttt[i][j].w=c[i][j]*2+1;
        ttt[i][j].v=sum[i][c[i][j]*2+1];
    }
    for(int i=1;i<=n;i++)
    for(int j=m;j>=0;j--)
    for(int k=1;k<=s;k++)
    {
        if(j>=ttt[i][k].w)
        {
            dp[j]=max(dp[j],dp[j-ttt[i][k].w]+ttt[i][k].v);
        }
    }
    cout<<dp[m];
    return 0;
}

8.4138挂饰

浅谈背包dp的相关例题

 

 每个挂件有4种可能:

1.有挂钩,价值为正(取它)

2.无挂钩,价值为负(打死都不取)

3.有挂钩,价值为负(考虑ing...)

4.无挂钩,价值为正(考虑ing....)

如何考虑呢,第三种情况就用01背包滚一遍,第四种情况就先排序,再和第三种情况解出的dp数组组合一下答案,取最大值。

详见代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=2010;
int x,y,cnt,cnt1;
int b[maxn];
bool cmp(const int A,const int B)
{
    return A>B;
}
int ans_1,ans_2,s[maxn];
int f[maxn];
struct
{
    int x,v;
}a[maxn];
int main()
{
    int n;
    ans_1=1;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>x>>y;
        if(x==0)
        {
            if(y<=0) continue ;
            else b[++cnt]=y;
        }
        else
        {
            if(y>=0) ans_1+=x-1,ans_2+=y;
            else a[++cnt1].x=x-1,a[cnt1].v=y;
        }
    }
    ans_1=min(ans_1,n);
    for(int i=0;i<=2000;i++) f[i]=-999999999;
    for(int i=0;i<=ans_1;i++)f[i]=0;
    for(int i=1;i<=cnt1;i++)
    for(int j=2000;j>=a[i].x;j--)
    f[j]=max(f[j],f[j-a[i].x]+a[i].v);
    sort(b+1,b+1+cnt,cmp);
    for(int i=1;i<=2000;i++)
    s[i]=s[i-1]+b[i];
    int ans=0;
    for(int i=0;i<=2000;i++)
    {
        ans=max(ans,f[i]+s[i]);
    }
    cout<<ans+ans_2;
    return 0;
}

9.2340

浅谈背包dp的相关例题

 

 呃,容量时n,价值为智商,体积为情商(可以交换),然后dp[j]就表示当前情商和为j时最大的智商和,因为j可能是个负数,所以整体将数组右移即可,然后就是背包滚一遍,最后统计答案。因为有负数,所以要从头到尾统计一遍。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1001;
const int maxm=401;
struct
{
    int iq,eq;
}a[8000000]; 
int cnt=0;
int n,x,y;
int dp[8000000];
int ans_s,ans_f,ans;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) 
    {
        int x,y;
        cin>>x>>y;
        if(x<=0 && y<=0) continue ;
        if(x>=0 && y>=0) ans_s+=x,ans_f+=y;
        else
        {
            a[++cnt].iq=x;
            a[cnt].eq=y;
        }
    }
    memset(dp,-0X3F,sizeof(dp));
    dp[400000]=0;
    for(int i=1;i<=n;i++)
    {
        if(a[i].iq>=0)
        {
            for(int j=800000;j>=a[i].iq;j--)
            dp[j]=max(dp[j],dp[j-a[i].iq]+a[i].eq);
         } 
         else
         {
             for(int j=0;j<=800000+a[i].iq;j++)
             dp[j]=max(dp[j],dp[j-a[i].iq]+a[i].eq);
         }
    }
    int ans=-999;
    for(int i=400000;i<=800000;i++)
    if(dp[i]>0)
    ans=max(ans,i+dp[i]-400000);
    ans+=ans_f+ans_s;
    if(ans<0) cout<<0;
    else cout<<ans;
    return 0;
}

10.4095

浅谈背包dp的相关例题

 

 因为原来是有顺序的从前往后,所以删除一个就会对整个dp数组有影响,那么我们就前缀后缀各滚一遍,用二维dp来存。

#include<bits/stdc++.h>
using namespace std;
int n,cw,cv,c,m;
int cnt;
long long f1[100010][1010];
long long f2[100010][1010];
struct
{
    int s,id;
}w[1000010],v[1000010];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>cw>>cv>>c;
        int now=1;
        while(now<=c)
        {
            w[++cnt].s=cw*now;
            v[cnt].s=cv*now;
            w[cnt].id=i;
            v[cnt].id=i;
            c-=now;
            now*=2;
        }
        if(c)
        {
            w[++cnt].s=cw*c;
            w[cnt].id=i;
            v[cnt].id=i;
            v[cnt].s=cv*c;
        }
    }
    cin>>m;
    n=cnt;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=1000;j++)
        f1[i][j]=f1[i-1][j];
        for(int j=1000;j>=w[i].s;j--)
        f1[i][j]=max(f1[i][j],f1[i-1][j-w[i].s]+v[i].s);
    }
    for(int i=n;i>=1;i--)
    {
        for(int j=0;j<=1000;j++)
        f2[i][j]=f2[i+1][j];
        for(int j=1000;j>=w[i].s;j--)
        f2[i][j]=max(f2[i][j],f2[i+1][j-w[i].s]+v[i].s);
    }
    for(int i=1;i<=m;i++)
    {
        int x,y;
        cin>>x>>y;
        x++;
        long long ans=0;
        int l=0;
        while(w[l+1].id<x && l<n) l++;
        int r=l;
        while(w[r+1].id<=x && r<n) r++;
        for(int j=0;j<=y;j++)
        {
            ans=max(ans,f1[l][j]+f2[r+1][y-j]);
        }
        cout<<ans<<endl;
    }
    return 0;
}

复习的也差不多了

可以再去试试:(就是不想写

1.纪念品(CSP2019PJ)(懒得讲)

2.emiya的饭(CSP2019TG)(准备留到容斥再讲)

3.皮配(十二省联考)(太难)

总结:

背包问题最精髓就在于,有“体积”,有“价值”,选或不选......然后就是分析题意,是什么类型的背包DP,这就看选择物品的个数限制了,或者分组背包(多选一)。(DP纯靠感觉qwq

浅谈背包dp的相关例题

 

浅谈背包dp的相关例题

上一篇:noip模拟测试34


下一篇:五大数据治理误区,直接影响你的数据治理成果!