本文介绍: 上述介绍常见三种背包问题思路和基础代码模板以及相应的优化。希望各位大佬能够给予指导以及相应的意见, 后续时间也会取更新更多更丰富的算法内容, 谢谢~

背包问题常见动态规划dp问题

下面用到符号:


0/1背包

下图是0/1背包的分析:
0/1背包问题的分析

题目 (0/1背包)

在这里插入图片描述

输入输出

输入输出

核心代码

for(int i = 1; i <= n; i++)
    for(int j = 0; j <= m; j++){ // 不难发现 f[i][] 都是依赖于 f[i-1][] 的
        f[i][j] = f[i-1][j];
    	if(v[i]<=j) f[i][j] = max(f[i][j], f[i-1][j-v[i]]+w[i]);    
}

优化: 降维 二维 -> 一维

需要注意的是在遍历背包容量的时候, 需要逆序遍历背包容量, 因为物品只能取一个和不取两种状态, 而逆序能够保证这个

完整代码如下:

#include<iostream>
#include<algorithm>

using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N]; // 物品体积 价值
int f[N]; 

int main(){
    scanf("%d%d",&n,&m);
    for(int i = 1; i<=n; i++) scanf("%d%d",&amp;v[i],&amp;w[i]);

    for(int i = 1; i<=n;i++){
       for(int j = m; j>=v[i]; j--){  // 每个物品只能使用一次 (逆序)
           f[j] = max(f[j], f[j-v[i]]+w[i]);
       }
    }
    printf("%dn",f[m]);
    return 0;
}

完全背包

下图是完全背包的分析:

完全背包分析

题目(完全背包 物品可以无限拿, 只要背包容量还能放得下)
题目和输入/输出
题目输入和输出

朴素做法(三层循环)

#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N];
int f[N][N];


int main(){
    cin>>n>>m;
    for(int i = 1; i <= n; i++) scanf("%d%d",&amp;v[i],&amp;w[i]);
    
    for(int i = 1; i<=n; i++){ // 列举物品体积
        for(int j = 0; j<=m; j++){  // 列举背包
            for(int k = 0; k*v[i] <= j; k++)// 决策 (背包体积有限)
                f[i][j] = max(f[i][j], f[i-1][j-k*v[i]]+w[i]*k);
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

优化:

(通过列举 f[i][j] 以及 f[i][j-v]两种情况, 发现很多相同之处, 只相差个v) => 化简

f[i]][j] = max(f[i-1][j], f[i-1][j-v]+w) 两种状态, 一个是取第i件物品, 另一个是不取第i件物品

 for(int i = 1; i <= n; i++){ // 列举物品
     for(int j = 0; j <= m; j++){ // 背包体积
         f[i][j] = f[i-1][j];
          if(j >= v[i]) f[i][j] = max(f[i][j], f[i][j-v[i]]+w[i]);
     }
 }

其实上述代码与0/1背包的代码有很多雷同的地方(都是两种状态, 要么取第i件, 要么不取), 只不过 0/1 背包是 f[i][j] = max(f[i][j], f[i-1][j-v[i]]+w[i]), 而完全背包是 f[i][j] = max(f[i][j], f[i][j-v[i]]+w[i]) (前一部分是不取第i件, 后面是取第i件的方案)
这个状态转移方程的含义可以这样理解

  1. 不取第 i 件物品:这种情况下,背包的最大价值保持不变,即为 f[i][j]。这实际上是上一个状态的值,因为我们没有添加新的物品。
  2. 取第 i 件物品:如果我们决定把第 i 件物品放入包中,那么背包的剩余容量变为 j-v[i]。因此,当前的总价值是放入这件物品之前的总价值 f[i][j-v[i]] 加上这件物品的价值 w[i]

对于上面代码的转化:

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

继续优化:

2维压缩成1维: 这里因为完全背包问题, 物品可以无数次, 所以第二层循环循环遍历背包容量时, 顺序遍历

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]<<endl;

多重背包

与上面的背包问题不同的是每个物品数量有上限: 0~s[i]
题目:
题目输入输出

状态划分: 两种 -> 取第i件物品 / 不取第i件物品

朴素做法

状态方程:f[i][j] = max(f[i-1][j-k*v[i]]+w[i]*k, f[i][j]), k = 0, 1, 2... s[i]

初级版 多重背包

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

 const int N = 110;
 int n, m;
 int s[N], v[N], w[N];
 int f[N][N]; // 1~N 个物品, 背包容量为N 所能达到的最大价值 (max)
 int main(){
     scanf("%d%d", &amp;n,&amp;m);
     for(int i = 1; i<=n; i++){ // 输入第i个物品的体积、价值和重量
         scanf("%d%d%d",&v[i],&w[i],&s[i]);
     }
     for(int i = 1; i <= n; i++){ // 物品
         for(int j = 0; j <= m; j++){ // 背包体积
             for(int k = 0; k <= s[i] && k*v[i] <= j; k++)
                 f[i][j] = max(f[i-1][j], f[i][j-k*v[i]]+w[i]*k);
         }
     }
     printf("%dn",f[n][m]);
     return 0;
 }

数据量比较大的时候, 上面的代码会出现 TLE, 因此我们需要对上述代码进行优化
优化:

困难版 多重背包
我们可以尝试展开f, 尝试寻找规律

f[i][j] = max(f[i-1][j], f[i-1][j-v]+w,...,f[i-1][j-sv]+sw)

f[i][j-v] = max( f[i-1][j-v], f[i-1][j-2v]+w,...+f[i-1][j-sv]+(s-1)w, f[i-1][j-s+1]v+sw)

但是从上式找不到其中规律,所以下述使用二进制拆解第i个物品个数s[i] 去优化上述代码

2^n的方式进行拆解包-> 01背包问题 s->logs 组, 且每组只能选一次 (拆分)

 #include<iostream>
 #include<algorithm>
 using namespace std;
 
 const int N = 25000, M = 2010; // 物品数量N NlogS 1000*log2000, 背包体积 M
 int n, m;
 int v[N], w[N]; // 物体体积, 价值
 int f[N]; // dp数组, 这里使用一维数组, 因为物品数量进行重组了
 
 /*
     多重背包的优化 (二进制对物品数量进行拆分)
     对物品进行拆分, 这样就少了一层循环(第三层k)
 */
 
 int main(){
     cin>>n>>m;
     int cnt = 0;
     for(int i = 1; i <= n; i++){
         int a, b, s;
         cin>>a>>b>>s; // 输入 第i种物品的体积, 价值
         int k = 1;
         while(k <= s){ // 拆分次数 s[i]!
             cnt++;
             v[cnt] = a * k; // k个第i件物品体积 k个 第i件物品体积为a
             w[cnt] = b * k;  // 更新第i个物品的价值
             s -= k;
             k *= 2;
         }
         if(s > 0){ // 剩余的(s个物品)为一件
             cnt++;
             v[cnt] = a*s; 
             w[cnt] = b*s;
         }
     }
 
     n = cnt; // 更新物品数量
     // 后继转为 0/1 背包问题, 每个物品 要么拿一次, 要么不拿
     for(int i = 1; i<=n; i++){
         for(int j = m; j >= v[i]; j--){
             f[j] = max(f[j], f[j-v[i]]+w[i]);
         }
     }
     cout<<f[m]<<endl;
     return 0;
 }

上述介绍常见三种背包问题思路和基础代码模板以及相应的优化。希望各位大佬能够给予指导以及相应的意见, 后续时间也会取更新更多更丰富的算法内容, 谢谢大家的观看♥~

原文地址:https://blog.csdn.net/weixin_46388660/article/details/134813156

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_49340.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注