Version: Next
完全背包问题
与 01 背包问题类似,只是每种物品的
数目无限
每种商品的上限与下限
- 下限:一个都不拿
- 上限:当前剩余容量 / 当前物品的重量 -> 最多可以拿这么多个
朴素完全背包解法
从 01 背包的代码修改得到完全背包的代码
- 在 01 背包中,用
i
循环 dp 数组的行 (物品),j
循环 dp 数组的列 (背包容量)- 在
j
内部再添加一个循环k
,k
的取值范围为0 ~ j/w[i]
,表示对当前物品i
拿 0、1、2、3...件
- 相应的:减去的是
k 倍的重量
,加上的是k 倍的价值
01背包循环部分for(int i = 0; i < weight.size(); i++) { // 遍历物品for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}}完全背包添加k循环for (int i = 1; i <= n; i++) {for (int j = m; j >= 0; j--) {for (int k = 1; k <= j / w[i]; k++) {dp[j] = Math.max(dp[j], dp[j - k * w[i]] + k * v[i]);}}}
- 显然时间复杂度太高了
优化写法
手推打表
- 01 背包打表
w 重量 v 价值 i\j 0 1 2 3 4 5 6 7 8 9 10 0 0 0 0 0 0 0 0 0 0 0 0 2 1 1 0 0 1 1 1 1 1 1 1 1 1 3 3 2 0 0 1 3 3 4 4 4 4 4 4 4 5 3 0 0 1 3 5 5 6 8 8 9 9 7 9 4 0 0 1 3 5 5 6 9 9 10 12
- 完全背包打表
w 重量 v 价值 i\j 0 1 2 3 4 5 6 7 8 9 10 0 0 0 0 0 0 0 0 0 0 0 0 2 1 1 0 0 1 1 2 2 3 3 4 4 5 3 3 2 0 0 1 3 3 4 6 6 7 9 9 4 5 3 0 0 1 3 5 5 6 8 10 10 11 7 9 4 0 0 1 3 5 5 6 9 10 10 12
- (j / w[i]) * v[i],无脑对着一个拿
- 优化为在拿了 n 个当前物品 i 的基础上看能不能再来一个 i
dp[i][j - w[i]] +v[i]
dp[i - 1][j]
不拿当前物品,上一轮拿法的价值优化完全背包for (int i = 1; i <= n; i++) {for (int j = m; j >= 0; j--) {for (int k = 1; k <= j / w[i]; k++) {dp[j] = Math.max(dp[j], dp[j - k * w[i]] + k * v[i]);}}}
01背包
->dp[i - 1][j -w[i]] + v[i]
完全背包
->dp[i][j - w[i]] + v[i]
滚动数组压缩 + 同行递推
dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i])
- 和 01 背包压缩后的状态转移方程一模一样
- 只是递推方向不同
- 优化掉第三个 for 循环
完全背包滚动数组+同行递推for (int i = 1; i <= n; i++) {for (int j = 0; j <= m; j++) {if (j >= w[i])dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);}}重要
二者的区别在于 递推方向
- 01 背包 j 从后往前,因为它用的是上一行的旧数据
- 完全背包 j 从前往后,因为它用的是当前行的新数据
- 注意限制
j >= w[i]
再优化:j 从 w[i] 开始遍历,去除 if 判断
for (int i = 1; i <= n; i++) {for (int j = w[i]; j <= m; j++) {dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);}}