Unbounded Knapsack
完全背包问题
1. 问题定义
给定两个数组:
volume[]:存储每个物品的体积(例如[0, 2, 3, 4])values[]:存储每个对应物品的价值(例如[0, 3, 4, 5])- 一个容量为
m的背包(例如10)
目标:选择物品(每种物品可以无限次选取),使得它们的总体积不超过m,且总价值最大化
与0-1背包问题的核心区别:完全背包中每种物品可以重复选择,这一特性直接影响动态规划的实现方式。
2. 动态规划(DP)方法
完全背包问题同样可以用动态规划高效解决,与0-1背包相比,状态定义相同但状态转移方式不同,时间复杂度仍为 \(O(n \times m)\)。
2.1 状态转移
dp[i][j] 表示使用前 i 个物品(每种可重复选取)和背包容量 j 可实现的最大价值
对于每个物品 i(从 1 到 n)和每个可能容量 j(从1到 m):
- 排除物品
i:dp[i][j] = dp[i - 1][j](价值与不使用物品i时相同) - 包含物品
i:如果volume[i] ≤ j(物品能放下),dp[i][j] = values[i] + dp[i][j - volume[i]](注意此处是dp[i][j - volume[i]],允许重复选取当前物品)
递推关系为:
if (volume[i] ≤ j)
dp[i][j] = max(dp[i - 1][j], values[i] + dp[i][j - volume[i]])
if (volume[i] > j)
dp[i][j] = dp[i - 1][j]
3. 示例演练
使用以下数据:
volume = [0, 2, 3, 4]values = [0, 3, 4, 5]m = 10
步骤1:初始化DP表

步骤2:计算过程解析
- 处理物品1(体积2,价值3)时,由于可重复选取,容量每增加2就能多放一个,价值递增3
- 处理物品2(体积3,价值4)时,例如容量5:max(之前的6, 容量2的3+4=7),更新为7
- 最终容量10的最大价值为15(选择5个物品1:5×3=15)
步骤3:最终结果
dp[3][10] = 15 → 最大价值为15
4. 代码实现(C++)
4.1 二维数组版本
#include <iostream>
using namespace std;
const int N = 1010;
int n, m; // n是物品数量,m是背包容量
int v[N], w[N]; // v=价值, w=体积
int f[N][N]; // dp表
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> v[i] >> w[i];
// 填充dp表
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
// 不选第i个物品
f[i][j] = f[i-1][j];
// 选第i个物品(可重复选)
if (j >= w[i])
f[i][j] = max(f[i][j], f[i][j - w[i]] + v[i]);
}
}
cout << f[n][m] << endl;
return 0;
}
4.2 一维数组优化版本
利用滚动数组优化空间,关键区别是正向遍历容量:
#include <iostream>
using namespace std;
const int N = 1010;
int n, m;
int f[N]; // 优化为一维数组
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int v, w;
cin >> v >> w;
// 正向遍历容量(允许重复选取当前物品)
for (int j = w; j <= m; j++)
f[j] = max(f[j], f[j - w] + v);
}
cout << f[m] << endl;
return 0;
}
5. 空间优化解析
核心原理
完全背包的一维优化与0-1背包的关键区别在于容量遍历方向:
- 0-1背包采用逆向遍历(
j从m到w[i]),确保每个物品只被使用一次 - 完全背包采用正向遍历(
j从w[i]到m),允许前面的更新结果被后面使用,实现物品的重复选取
空间复杂度对比
- 二维版本:
O(n × m) - 一维版本:
O(m),极大节省内存空间,尤其适合处理大规模问题

浙公网安备 33010602011771号