背包九讲 详细解析与 C++ 实现
目录
1,背包问题
2. 完全背包问题
3. 多重背包问题
4. 混合三种背包问题
5. 二维费用的背包问题
6. 分组背包问题
7. 有依赖的背包问题
8. 背包问题求方案数
9. 背包问题求具体方案
总结
1,背包问题
问题描述
有 N 件物品和一个容量为 V 的背包。第 i 件物品的体积是 c [i],价值是 w [i]。求解将哪些物品装入背包可使价值总和最大。每件物品只能使用一次。
基本思路
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。
用子问题定义状态:令 dp [i][v] 表示前 i 件物品恰放入一个容量为 v 的背包可以获得的最大价值。
状态转移方程:
- dp[i][v] = max(dp[i-1][v], dp[i-1][v-c[i]] + w[i])
解释:
- "将前 i 件物品放入容量为 v 的背包中" 这个子问题,若只考虑第 i 件物品的策略(放或不放)
- 如果不放第 i 件物品,那么问题就转化为 "前 i-1 件物品放入容量为 v 的背包中"
- 如果放第 i 件物品,那么问题就转化为 "前 i-1 件物品放入剩下的容量为 v-c [i] 的背包中"
空间优化
上面的方法时间和空间复杂度均为 O (N*V),其中时间复杂度基本不能再优化,但空间复杂度可以优化到 O (V)。
优化思路:如果我们只用一个数组 dp [v],并且在计算时采用逆序遍历的方式,就可以实现空间优化。
代码实现
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 01背包问题
// 参数:物品数量n,背包容量v,物品体积数组c,物品价值数组w
// 返回:最大价值
int knapsack01(int n, int v, vector<int>& c, vector<int>& w) {// dp数组,dp[j]表示容量为j的背包能获得的最大价值vector<int> dp(v + 1, 0);// 遍历每件物品for (int i = 0; i < n; i++) {// 逆序遍历背包容量,防止物品被多次使用for (int j = v; j >= c[i]; j--) {// 状态转移方程:取放或不放当前物品的最大值dp[j] = max(dp[j], dp[j - c[i]] + w[i]);}}return dp[v];
}int main() {// 示例int n = 3; // 物品数量int v = 5; // 背包容量vector<int> c = {2, 3, 4}; // 物品体积vector<int> w = {3, 4, 5}; // 物品价值int max_value = knapsack01(n, v, c, w);cout << "01背包的最大价值为:" << max_value << endl; // 预期输出:7return 0;
}
代码解释
- 我们使用一个一维数组
dp
来存储不同容量背包的最大价值 - 外层循环遍历每件物品
- 内层循环逆序遍历背包容量,这是为了保证每件物品只被考虑一次
- 对于每个容量
j
,我们都有两种选择:不放当前物品(保持dp[j]
不变)或放当前物品(更新为dp[j - c[i]] + w[i]
) - 最终
dp[v]
就是容量为 v 的背包能获得的最大价值
2. 完全背包问题
问题描述
有 N 种物品和一个容量为 V 的背包。第 i 种物品的体积是 c [i],价值是 w [i]。求解将哪些物品装入背包可使价值总和最大。每种物品可以无限次使用。
基本思路
完全背包与 01 背包的区别在于物品可以无限次使用,所以状态转移方程有所不同。
基本状态转移方程:
- dp[i][v] = max(dp[i-1][v], dp[i][v-c[i]] + w[i])
与 01 背包的区别在于,当考虑放入第 i 件物品时,我们使用的是 dp [i][v-c [i]] 而不是 dp [i-1][v-c [i]],这是因为第 i 件物品可以多次使用。
空间优化
同样可以优化到 O (V) 的空间复杂度,但与 01 背包不同的是,这里采用正序遍历。
代码实现
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 完全背包问题
// 参数:物品数量n,背包容量v,物品体积数组c,物品价值数组w
// 返回:最大价值
int completeKnapsack(int n, int v, vector<int>& c, vector<int>& w) {// dp数组,dp[j]表示容量为j的背包能获得的最大价值vector<int> dp(v + 1, 0);// 遍历每件物品for (int i = 0; i < n; i++) {// 正序遍历背包容量,允许物品被多次使用for (int j = c[i]; j <= v; j++) {// 状态转移方程dp[j] = max(dp[j], dp[j - c[i]] + w[i]);}}return dp[v];
}int main() {// 示例int n = 3; // 物品数量int v = 5; // 背包容量vector<int> c = {2, 3, 4}; // 物品体积vector<int> w = {3, 4, 5}; // 物品价值int max_value = completeKnapsack(n, v, c, w);cout << "完全背包的最大价值为:" << max_value << endl; // 预期输出:7(选择两个体积为2的物品,价值3+3=6?不,这里应该是选择一个体积为2和一个体积为3的物品,总价值3+4=7)return 0;
}
代码解释
- 与 01 背包类似,我们也使用一维数组
dp
- 区别在于内层循环采用正序遍历,这样就允许一件物品被多次选择
- 当考虑容量
j
时,dp[j - c[i]]
可能已经包含了第 i 件物品,因此可以实现物品的多次使用 - 最终
dp[v]
就是容量为 v 的背包能获得的最大价值
3. 多重背包问题
问题描述
有 N 种物品和一个容量为 V 的背包。第 i 种物品最多有 m [i] 件可用,体积是 c [i],价值是 w [i]。求解将哪些物品装入背包可使价值总和最大。
基本思路
多重背包问题介于 01 背包和完全背包之间,物品既不是只能用一次,也不是可以无限使用,而是有固定的使用次数限制。
最直接的思路是将多重背包转化为 01 背包:把第 i 种物品看作 m [i] 件独立的物品,然后使用 01 背包的方法求解。
但这种方法效率较低,更优的方法是使用二进制优化:将 m [i] 分解为几个 2 的幂之和,这样可以用较少的物品数量表示所有可能的选择数量。
代码实现
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 多重背包问题(二进制优化)
// 参数:物品数量n,背包容量v,物品体积数组c,物品价值数组w,物品数量数组m
// 返回:最大价值
int multipleKnapsack(int n, int v, vector<int>& c, vector<int>& w, vector<int>& m) {// 二进制优化:将多重背包转化为01背包vector<int> new_c, new_w;for (int i = 0; i < n; i++) {int count = m[i];// 分解为2的幂for (int k = 1; count > 0; k *= 2) {int take = min(k, count);new_c.push_back(c[i] * take);new_w.push_back(w[i] * take);count -= take;}}// 现在使用01背包求解vector<int> dp(v + 1, 0);for (int i = 0; i < new_c.size(); i++) {for (int j = v; j >= new_c[i]; j--) {dp[j] = max(dp[j], dp[j - new_c[i]] + new_w[i]);}}return dp[v];
}int main() {// 示例int n = 3; // 物品数量int v = 10; // 背包容量vector<int> c = {2, 3, 4}; // 物品体积vector<int> w = {3, 4, 5}; // 物品价值vector<int> m = {2, 3, 1}; // 物品数量限制int max_value = multipleKnapsack(n, v, c, w, m);cout << "多重背包的最大价值为:" << max_value << endl; // 预期输出:13return 0;
}
代码解释
- 二进制优化的核心思想是将数量为 m [i] 的物品分解为多个 "打包物品"
- 例如,若 m [i] = 13,可以分解为 1、2、4、6(1+2+4+6=13)
- 这样分解后,通过选择不同的 "打包物品",可以组合出 0 到 13 之间的任何数量
- 分解完成后,问题就转化为 01 背包问题,可以用 01 背包的解法求解
- 这种优化大大减少了物品数量,提高了算法效率
4. 混合三种背包问题
问题描述
有 N 种物品和一个容量为 V 的背包。每种物品可能是 01 背包物品(只能用一次)、完全背包物品(可以无限次使用)或多重背包物品(有使用次数限制)。求解将哪些物品装入背包可使价值总和最大。
基本思路
这种问题是前三种背包问题的混合,可以根据物品的类型分别处理:
- 对于 01 背包物品,使用 01 背包的处理方法(逆序遍历)
- 对于完全背包物品,使用完全背包的处理方法(正序遍历)
- 对于多重背包物品,先进行二进制优化,再用 01 背包的处理方法
代码实现
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 物品类型:0-01背包,1-完全背包,2-多重背包
struct Item {int type; // 物品类型int c; // 体积int w; // 价值int m; // 数量(仅用于多重背包)
};// 混合三种背包问题
// 参数:物品列表items,背包容量v
// 返回:最大价值
int mixedKnapsack(vector<Item>& items, int v) {vector<int> dp(v + 1, 0);for (auto& item : items) {if (item.type == 0) {// 01背包:逆序遍历for (int j = v; j >= item.c; j--) {dp[j] = max(dp[j], dp[j - item.c] + item.w);}} else if (item.type == 1) {// 完全背包:正序遍历for (int j = item.c; j <= v; j++) {dp[j] = max(dp[j], dp[j - item.c] + item.w);}} else if (item.type == 2) {// 多重背包:二进制优化int count = item.m;for (int k = 1; count > 0; k *= 2) {int take = min(k, count);int c = item.c * take;int w = item.w * take;// 按01背包处理for (int j = v; j >= c; j--) {dp[j] = max(dp[j], dp[j - c] + w);}count -= take;}}}return dp[v];
}int main() {// 示例int v = 10; // 背包容量vector<Item> items = {{0, 2, 3, 0}, // 01背包物品:体积2,价值3{1, 3, 4, 0}, // 完全背包物品:体积3,价值4{2, 4, 5, 2} // 多重背包物品:体积4,价值5,最多2个};int max_value = mixedKnapsack(items, v);cout << "混合背包的最大价值为:" << max_value << endl; // 预期输出:12return 0;
}
代码解释
- 我们定义了一个
Item
结构体,包含物品类型和相应属性 - 遍历每种物品时,根据物品类型采用不同的处理方式
- 对于 01 背包物品,使用逆序遍历更新 dp 数组
- 对于完全背包物品,使用正序遍历更新 dp 数组
- 对于多重背包物品,先进行二进制优化,再按 01 背包处理
- 这种方法可以高效处理三种类型混合的背包问题
5. 二维费用的背包问题
问题描述
二维费用的背包问题是指:对于每件物品,具有两种不同的费用(例如重量和体积);选择这件物品必须同时付出这两种费用;对于每种费用都有一个可付出的最大值(背包容量)。求解选择物品的总费用不超过两种费用的最大值,且价值总和最大。
基本思路
与一维背包问题类似,只是需要增加一维来表示另一种费用。
状态定义:令 dp [i][j][k] 表示前 i 件物品,付出的第一种费用为 j,第二种费用为 k 时的最大价值。
状态转移方程:
- dp[i][j][k] = max(dp[i-1][j][k], dp[i-1][j-c1[i]][k-c2[i]] + w[i])
空间优化后,可以使用二维数组 dp [j][k],表示付出的第一种费用为 j,第二种费用为 k 时的最大价值。
代码实现
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 二维费用的背包问题(01背包)
// 参数:物品数量n,两种容量v1、v2,两种费用数组c1、c2,价值数组w
// 返回:最大价值
int knapsack2D(int n, int v1, int v2, vector<int>& c1, vector<int>& c2, vector<int>& w) {// dp[j][k]表示第一种费用为j,第二种费用为k时的最大价值vector<vector<int>> dp(v1 + 1, vector<int>(v2 + 1, 0));for (int i = 0; i < n; i++) {// 二维费用都需要逆序遍历for (int j = v1; j >= c1[i]; j--) {for (int k = v2; k >= c2[i]; k--) {dp[j][k] = max(dp[j][k], dp[j - c1[i]][k - c2[i]] + w[i]);}}}return dp[v1][v2];
}int main() {// 示例:假设两种费用分别是重量和体积int n = 3; // 物品数量int max_weight = 10; // 最大重量限制int max_volume = 8; // 最大体积限制vector<int> weight = {3, 4, 2}; // 物品重量vector<int> volume = {2, 3, 4}; // 物品体积vector<int> value = {5, 6, 7}; // 物品价值int max_value = knapsack2D(n, max_weight, max_volume, weight, volume, value);cout << "二维费用背包的最大价值为:" << max_value << endl; // 预期输出:13return 0;
}
代码解释
- 使用二维数组
dp[j][k]
表示两种费用分别为 j 和 k 时的最大价值 - 外层循环遍历每件物品
- 内层两个循环分别对两种费用进行逆序遍历(对于 01 背包)
- 状态转移时需要同时考虑两种费用的消耗
- 最终
dp[v1][v2]
就是两种费用分别不超过 v1 和 v2 时的最大价值
这种思路可以扩展到更多维的费用问题,但随着维度增加,空间和时间复杂度都会显著增加。
6. 分组背包问题
问题描述
有 N 组物品和一个容量为 V 的背包。每组物品中最多只能选择一件物品。第 i 组的第 j 件物品的体积是 c [i][j],价值是 w [i][j]。求解选择哪些物品装入背包可使价值总和最大。
基本思路
分组背包问题的关键是每组物品中最多选择一件。
状态定义:令 dp [k][v] 表示考虑前 k 组物品,容量为 v 的背包能获得的最大价值。
状态转移方程:
- dp [k][v] = max (dp [k-1][v], dp [k-1][v-c [k][j]] + w [k][j]) 对于第 k 组的所有 j
空间优化后,可以使用一维数组 dp [v],但需要注意遍历顺序。
代码实现
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 分组背包问题
// 参数:组数n,背包容量v,每组物品的体积groups_c,每组物品的价值groups_w
// 返回:最大价值
int groupKnapsack(int n, int v, vector<vector<int>>& groups_c, vector<vector<int>>& groups_w) {vector<int> dp(v + 1, 0);// 遍历每组物品for (int i = 0; i < n; i++) {// 逆序遍历背包容量for (int j = v; j >= 0; j--) {// 遍历当前组的每件物品for (int k = 0; k < groups_c[i].size(); k++) {int c = groups_c[i][k];int w = groups_w[i][k];if (j >= c) {dp[j] = max(dp[j], dp[j - c] + w);}}}}return dp[v];
}int main() {// 示例int n = 3; // 组数int v = 10; // 背包容量// 每组物品的体积vector<vector<int>> groups_c = {{2, 3}, // 第1组物品体积{4, 5}, // 第2组物品体积{6, 7} // 第3组物品体积};// 每组物品的价值vector<vector<int>> groups_w = {{3, 4}, // 第1组物品价值{5, 6}, // 第2组物品价值{7, 8} // 第3组物品价值};int max_value = groupKnapsack(n, v, groups_c, groups_w);cout << "分组背包的最大价值为:" << max_value << endl; // 预期输出:12return 0;
}
代码解释
- 使用一维数组
dp[v]
表示容量为 v 的背包能获得的最大价值 - 外层循环遍历每组物品
- 中层循环逆序遍历背包容量,确保每组物品最多选择一件
- 内层循环遍历当前组的每件物品,考虑选择其中一件的情况
- 对于每个容量和每件物品,更新 dp [j] 为不选当前物品或选择当前物品的最大值
- 最终
dp[v]
就是容量为 v 的背包能获得的最大价值
7. 有依赖的背包问题
问题描述
这种问题的物品之间存在依赖关系,即物品 i 依赖于物品 j,表示若要选择物品 i,则必须选择物品 j。这种依赖关系形成一个森林,通常是一棵树。
基本思路
有依赖的背包问题可以转化为树状结构的动态规划问题:
- 先处理子树,再处理父节点
- 对于每个节点,考虑两种情况:选或不选
- 若不选父节点,则其所有子节点都不能选
- 若选父节点,则可以选择其某些子树
我们可以使用一个 "泛化物品" 的概念来处理:一个节点的泛化物品是指在选择该节点的前提下,选择其子树的各种可能组合所形成的物品集合。
代码实现
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>using namespace std;const int MAX_V = 100; // 最大背包容量// 物品结构
struct Item {int c; // 体积int w; // 价值vector<int> children; // 子节点
};vector<Item> items; // 物品列表
int dp[MAX_V + 1]; // dp数组
int temp[MAX_V + 1]; // 临时数组,用于处理子树// 深度优先搜索处理有依赖的背包
void dfs(int u, int v) {// 初始化:只选择当前物品for (int j = items[u].c; j <= v; j++) {dp[j] = items[u].w;}// 处理每个子节点for (int child : items[u].children) {// 复制当前dp到temp,保存不选子树的状态memcpy(temp, dp, sizeof(dp));// 递归处理子树,注意要预留当前物品的体积dfs(child, v - items[u].c);// 合并结果:选择子树中的物品for (int j = v; j >= items[u].c; j--) {for (int k = 0; k <= j - items[u].c; k++) {dp[j] = max(dp[j], temp[j - k] + dp[items[u].c + k] - items[u].w);}}// 恢复temp到dp,为下一个子树做准备memcpy(dp, temp, sizeof(dp));}
}// 有依赖的背包问题
// 参数:根节点root,背包容量v
// 返回:最大价值
int dependentKnapsack(int root, int v) {memset(dp, 0, sizeof(dp));dfs(root, v);return dp[v];
}int main() {// 示例:构建一个有依赖关系的物品树// 根节点0,有两个子节点1和2// 节点1有一个子节点3items = {{2, 3, {1, 2}}, // 节点0:体积2,价值3,子节点1、2{3, 4, {3}}, // 节点1:体积3,价值4,子节点3{4, 5, {}}, // 节点2:体积4,价值5,无子节点{1, 2, {}} // 节点3:体积1,价值2,无子节点};int v = 10; // 背包容量int max_value = dependentKnapsack(0, v);cout << "有依赖的背包的最大价值为:" << max_value << endl; // 预期输出:12return 0;
}
代码解释
- 我们使用树结构来表示物品之间的依赖关系,每个节点包含体积、价值和子节点列表
- 使用深度优先搜索 (DFS) 处理树,先处理子节点,再处理父节点
- 对于每个节点,我们先初始化只选择该节点的情况
- 然后递归处理每个子节点,并将子节点的结果与当前节点合并
- 合并时使用一个临时数组保存不选当前子树的状态,再与选择子树的状态比较
- 最终
dp[v]
就是容量为 v 的背包能获得的最大价值
这种方法可以处理任意树状依赖关系的背包问题,但实现相对复杂一些。
8. 背包问题求方案数
问题描述
对于前面讨论的各种背包问题,有时我们不仅想知道最大价值是多少,还想知道获得最大价值的方案有多少种。
基本思路
我们可以增加一个数组count
,其中count[v]
表示容量为 v 的背包获得最大价值的方案数。
具体做法:
- 先计算出最大价值的 dp 数组
- 再计算方案数 :
- 若选择物品 i 后能达到最大价值,则累加方案数
- 注意处理价值相等的情况
代码实现(以 01 背包为例)
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 01背包问题求方案数
// 参数:物品数量n,背包容量v,物品体积数组c,物品价值数组w
// 返回:pair(最大价值, 方案数)
pair<int, int> knapsack01Count(int n, int v, vector<int>& c, vector<int>& w) {const int MOD = 1e9 + 7; // 取模,防止方案数过大// dp[j]表示容量为j的背包能获得的最大价值vector<int> dp(v + 1, 0);// count[j]表示容量为j的背包获得最大价值的方案数vector<int> count(v + 1, 0);// 初始化:容量为0时,价值为0,方案数为1count[0] = 1;for (int i = 0; i < n; i++) {for (int j = v; j >= c[i]; j--) {int new_value = dp[j - c[i]] + w[i];if (new_value > dp[j]) {// 发现更大的价值,更新价值和方案数dp[j] = new_value;count[j] = count[j - c[i]];} else if (new_value == dp[j]) {// 价值相等,累加方案数count[j] = (count[j] + count[j - c[i]]) % MOD;}}}// 寻找最大价值int max_value = 0;for (int j = 0; j <= v; j++) {max_value = max(max_value, dp[j]);}// 累加所有能达到最大价值的方案数int total_count = 0;for (int j = 0; j <= v; j++) {if (dp[j] == max_value) {total_count = (total_count + count[j]) % MOD;}}return {max_value, total_count};
}int main() {// 示例int n = 3; // 物品数量int v = 5; // 背包容量vector<int> c = {2, 3, 4}; // 物品体积vector<int> w = {3, 4, 5}; // 物品价值auto [max_value, total_count] = knapsack01Count(n, v, c, w);cout << "01背包的最大价值为:" << max_value << endl; // 预期输出:7cout << "获得最大价值的方案数为:" << total_count << endl; // 预期输出:1return 0;
}
代码解释
- 我们增加了一个
count
数组来记录获得最大价值的方案数 - 初始化时,容量为 0 的方案数为 1(什么都不选)
- 在更新 dp [j] 时:
- 若发现更大的价值,更新 dp [j] 并将方案数设为 count [j - c [i]]
- 若价值相等,则将方案数累加 count [j - c [i]]
- 在更新 dp [j] 时:
- 最后需要找出最大价值,并累加所有能达到该价值的方案数
- 为了防止方案数过大,我们使用了取模操作
这种方法可以扩展到其他类型的背包问题,只需在相应的 dp 更新过程中加入方案数的计算即可。
9. 背包问题求具体方案
问题描述
有时我们不仅想知道背包问题的最大价值和方案数,还想知道具体选择了哪些物品。
基本思路
要求具体方案,我们需要记录下每个状态是如何得到的,即选择了哪个物品。
具体做法:
- 先计算出完整的 dp 数组
- 然后从最终状态(通常是 dp [v])回溯,判断每个物品是否被选中
- 记录被选中的物品,即为一种最优方案
代码实现(以 01 背包为例)
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>using namespace std;// 01背包问题求具体方案
// 参数:物品数量n,背包容量v,物品体积数组c,物品价值数组w
// 返回:pair(最大价值, 选中的物品索引列表)
pair<int, vector<int>> knapsack01Solution(int n, int v, vector<int>& c, vector<int>& w) {// 使用二维数组以便回溯vector<vector<int>> dp(n + 1, vector<int>(v + 1, 0));// 计算dp数组for (int i = 1; i <= n; i++) {for (int j = 0; j <= v; j++) {// 不选第i件物品dp[i][j] = dp[i - 1][j];// 选第i件物品(注意索引转换,物品从1开始编号)if (j >= c[i - 1] && dp[i][j] < dp[i - 1][j - c[i - 1]] + w[i - 1]) {dp[i][j] = dp[i - 1][j - c[i - 1]] + w[i - 1];}}}// 回溯寻找具体方案vector<int> selected;int current_v = v;for (int i = n; i >= 1; i--) {// 判断第i件物品是否被选中if (current_v >= c[i - 1] && dp[i][current_v] == dp[i - 1][current_v - c[i - 1]] + w[i - 1]) {selected.push_back(i - 1); // 记录选中的物品索引(0-based)current_v -= c[i - 1]; // 减去该物品的体积}}// 反转选中物品的顺序,使其与输入顺序一致reverse(selected.begin(), selected.end());return {dp[n][v], selected};
}int main() {// 示例int n = 3; // 物品数量int v = 5; // 背包容量vector<int> c = {2, 3, 4}; // 物品体积vector<int> w = {3, 4, 5}; // 物品价值auto [max_value, selected] = knapsack01Solution(n, v, c, w);cout << "01背包的最大价值为:" << max_value << endl; // 预期输出:7cout << "选中的物品索引为:";copy(selected.begin(), selected.end(), ostream_iterator<int>(cout, " ")); // 预期输出:0 1cout << endl;return 0;
}
代码解释
- 为了方便回溯,我们使用了二维 dp 数组,保留了所有状态
- 计算 dp 数组时,采用了物品从 1 开始编号的方式,便于回溯
- 回溯过程从最后一件物品开始,判断是否被选中:
- 若 dp [i][current_v] == dp [i-1][current_v - c [i-1]] + w [i-1],则说明第 i 件物品被选中
- 记录选中的物品,并减去其体积
- 最后将选中物品的顺序反转,使其与输入顺序一致
- 返回最大价值和选中物品的索引列表
这种方法可以得到一种最优方案,如果有多种最优方案,通常会得到字典序最小或最大的方案,具体取决于实现细节。如果需要所有最优方案,则需要更复杂的处理。
总结
背包问题是动态规划中的经典问题,本文详细介绍了背包九讲中的各种问题及其 C++ 实现:
- 01 背包:每件物品只能用一次,采用逆序遍历优化空间
- 完全背包:每件物品可以无限使用,采用正序遍历
- 多重背包:每件物品有使用次数限制,采用二进制优化
- 混合背包:综合处理前三种类型的物品
- 二维费用背包:每件物品有两种费用,使用二维 dp 数组
- 分组背包:每组物品最多选一件,按组处理
- 有依赖的背包:物品间有依赖关系,使用树状 DP 处理
- 背包问题求方案数:在求最大价值的同时计算方案数量
- 背包问题求具体方案:通过回溯找出最优方案的具体组成
这些问题虽然形式各异,但核心思想都是动态规划,通过定义合适的状态和状态转移方程来求解。掌握这些问题的解法,有助于理解和解决更复杂的动态规划问题。