常见背包问题

强烈推荐大佬博客dd大牛的《背包九讲》!!!

01背包问题

01背包问题:指的是每件物品只有一个,背包有容量(或者重量)限制,每个物品有大小(或者重量)和价值,求背包在容量(或者重量)限制下能装入物品的价值和最大是多少。

f[i][j],代表考虑前i个物品,当前背包容量为j时,背包能装入的最大价值。
f[i][j]=max{f[i][j-1],f[i-1][j-v[i]]+w[i]};表示,
不选第i个物品和选第i个物品,在背包容量为j时,那种情况背包内的价值最大
可以画二维表格,更容易理解

01背包问题模板题

代码如下:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N=1e3+10;
int f[N][N];
int v[N],w[N];
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++) 
        for(int j=1;j<=m;j++){
            f[i][j]=f[i-1][j];
            if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
    cout<<f[n][m];
    return 0;
}

优化空间

考虑对空间复杂度进行优化,f[i][j]是由f[i-1][i~j]来产生的,所以我们只需要保留上一行的信息,不需要其他行的信息。考虑用1行来存储,那么如何获得上一行的信息呢?每一行倒着遍历即可,这样一行原本信息f[i][j]=f[i-1][j],判断是否拿第i个物品时,j>j-v[i],也不会产生冲突。

代码如下:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N=1e3+10;
int f[N];
int v[N],w[N];
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++)
        for(int j=m;j>=v[i];j--)//从大到小枚举,保证第i次是通过i-1得来的
            f[j]=max(f[j],f[j-v[i]]+w[i]);
    cout<<f[m];
    return 0;
}

完全背包问题

完全背包问题在01背包问题上的改变是不限制物品的数量,即每样物品有无数个,求背包能装入物品的价值和最大是多少

考虑从01背包推导至完全背包

for(int i=1;i<=n;i++)
    for(int j=m;j>=v[i];j--)//从大到小枚举,保证第i次是通过i-1得来的
        for(int k=1;k*v[i]<=j;k++)  f[j]=max(f[j],f[j-v[i]*k]+w[i]*k);

考虑另一种思路:

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

这个代码与01背包问题的代码的差别只是在遍历背包容量时,是从小到大遍历的,思考一下,为什么要这样做?

01背包问题从大到小遍历,是为了背包容量为 j 时,保证此时的f[ i ][ j ]是从f[i-1][ j (不选)]或者f[i-1][j-v[i]]+w[i](选一个第 i 个物品)得来的,也就是说明,在考虑第 i 个物品,背包容量为 j 时,该物品 i 只会被考虑加入背包一次。

那么为什么 j 从小到大遍历后,就会转化为完全背包问题呢?
j 从小到大遍历时,当前的f[ i ][ j ]是从f[i-1][ j ](不选)或者f[ i ][j-v[ i ]]+w[ i ](选择一个或多个第 i 个物品 )得来的,当f[ i ][ j ]由f[ i ][j-v[ i ]]+w[ i ]的来时(f[ i ][j-v[ i ]]也可能选择了第 i 个物品),此时第 i 个物品被选择了多个。这样就从小到大遍历 j 就将第 i 个物品给考虑了多次。

完全背包问题模板题

代码如下:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N=1e3+10;
int f[N];
int v[N],w[N];
int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++)
        for(int j=v[i];j<=m;j++)
            f[j]=max(f[j],f[j-v[i]]+w[i]);
    cout<<f[m];
    return 0;
}

多重背包问题

多重背包问题在01背包的基础上,物品有了数量的限制。

转化为01背包问题求解

可以将多重背包问题转化为01背包问题来求解,只需要在01背包的基础上怎加一层循环,来遍历物品的数量即可,该方法只能用于数据范围比较小的情况。
多重背包问题 I 模板题

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=1e2+10;

int f[N];
int v[N],w[N],cnt[N];

int main(){
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>cnt[i];
    for(int i=1;i<=n;i++)
        for(int j=m;j>=1;j--)
            for(int k=1;k<=cnt[i];k++) 
                if(j>=k*v[i]) f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
    cout<<f[m];
    
    return 0;
}

二进制优化

多重背包二进制优化解法的思路仍然是将多重背包问题给分解成01背包问题求解,如果将物品给拆成一份一份的情况,那么就跟上一个解法一样。二进制优化就是在拆分方法上做优化,我们不必要将物品数量给拆成一份一份的,用二进制拆分的方法。
将一个数n拆分成:1+2+4+…+2^k+剩余的数
例如:7=1+2+4,8=1+2+4+1
可以证明,0-n中的任意一个数,均可以用若干n拆分出的系数来表示
例如:7=1+2+4
1=1
2=2
3=1+2
4=4
5=1+4
6=2+4
7=7

这样便可将问题给优化了,当物品数量为s时,只需要拆分成log(s)个数而不是s个,在代码中能有效降低时间复杂度
多重背包问题 II 模板题

代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=2e5+10;

int f[N];

int main(){
    int n,m;
    cin>>n>>m;
    vector<pair<int,int> > ve;
    for(int i=0;i<n;i++){//拆分
        int v,w,s;
        scanf("%d%d%d",&v,&w,&s);
        for(int j=1;j<=s;j*=2){
            s-=j;
            ve.push_back(make_pair(v*j,w*j));
        }
        if(s) ve.push_back(make_pair(v*s,w*s));
    }
    for(int i=0;i<ve.size();i++)//按照01背包计算
        for(int j=m;j>=ve[i].first;j--)
            f[j]=max(f[j],f[j-ve[i].first]+ve[i].second);
    cout<<f[m];
    
    return 0;
}

单调队列优化

当数据范围再次变大时,考虑更加优化的方法—利用单调队列优化
多重背包问题的单调队列优化方法,就是传说中的《男人八题》

这个问题比较难,我大概能理解,但是讲不好,建议找视频学习。
多重背包问题 III 模板题

代码如下:

#include <iostream>
#include <cstring>
using namespace std;
const int N = 20010;
int dp[N], pre[N], q[N];
int n, m;
int main() {
    cin >> n >> m;
    for (int i = 0; i < n; ++i) {
        memcpy(pre, dp, sizeof(dp));
        int v, w, s;
        cin >> v >> w >> s;
        for (int j = 0; j < v; ++j) {
            int head = 0, tail = -1;
            for (int k = j; k <= m; k += v) {
                if (head <= tail && k - s*v > q[head])
                    ++head;
                while (head <= tail && pre[q[tail]] - (q[tail] - j)/v * w <= pre[k] - (k - j)/v * w)
                    --tail;
                if (head <= tail)
                    dp[k] = max(dp[k], pre[q[head]] + (k - q[head])/v * w);
                q[++tail] = k;
            }
        }
    }
    cout << dp[m] << endl;
    return 0;
}

混合背包问题

混合背包问题就是将上述三种背包问题混合在一起的情况
其中多重背包问题可二进制拆分成01背包问题
01背包问题与完全背包问题分开计算即可
混合背包问题模板题

代码如下:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=1e3+10;
int n,m;
int f[N];//多重背包问题转化为01背包问题,分类讨论
struct node{
    int kind;
    int v,w;
};
int main()
{
    vector<node>v;
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
    {
        int x,y,s;
        scanf("%d%d%d",&x,&y,&s);
        if(s<0) v.push_back({-1,x,y});
        else if(s==0) v.push_back({0,x,y});
        else
        {
            for(int k=1;k<=s;k*=2)
            {
                s-=k;
                v.push_back({-1,k*x,k*y});
            }
            if(s) v.push_back({-1,s*x,s*y});
        }
    }
    vector<node>::iterator it;
    for(it=v.begin();it!=v.end();it++)
        if((*it).kind==-1) 
            for(int j=m;j>=(*it).v;j--)
                f[j]=max(f[j],f[j-(*it).v]+(*it).w);
        else 
            for(int j=(*it).v;j<=m;j++)
                f[j]=max(f[j],f[j-(*it).v]+(*it).w);
    printf("%d\n",f[m]);
    return 0;
}

二维费用的背包问题

二维费用的背包问题指的是背包有多个限制,这个问题比较简单,只需要稍微改变一下01背包问题即可,容易理解
二维费用的背包问题模板题

代码如下:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N=110;
int f[N][N];
int n,v,m;
int main()
{
    cin>>n>>v>>m;
    for(int i=0;i<n;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        for(int j=v;j>=x;j--)
            for(int k=m;k>=y;k--)
                f[j][k]=max(f[j][k],f[j-x][k-y]+z);
    }
    cout<<f[v][m];
    return 0;
}

分组背包问题

给定n组物品,每组物品若干个,同一组物品最多只能选择一个。每组s个,一共s+1种选择(不选也算一种),求背包在容量(或者重量)限制下能装入物品的价值和最大是多少。

分组背包问题就是在01背包的基础上,增加对组内每个物品的遍历即可
分组背包问题模板题

代码如下:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=110;
int f[N];
int v[N],w[N];
int n,m;
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++){
        int t;
        scanf("%d",&t);
        for(int j=0;j<t;j++) scanf("%d%d",&v[j],&w[j]);
        for(int j=m;j>=0;j--)
            for(int k=0;k<t;k++)
                if(v[k]<=j)  f[j]=max(f[j],f[j-v[k]]+w[k]);
    }
    cout<<f[m];
    return 0;
}

总结

各种背包问题的原型是01背包问题,所以要弄明白01背包问题。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chp的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值