完全背包
有 N 件物品和一个最多能背重量为 W 的背包。第 i 件物品的重量是 weight[i],得到的价值是 value[i]。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
完全背包和 01 背包问题唯一不同的地方就是,每种物品有无限件。
例子:
背包最大重量为 4,物品为:
| 重量 | 价值 | |
|---|---|---|
| 物品 0 | 1 | 15 |
| 物品 1 | 3 | 20 |
| 物品 2 | 4 | 30 |
每件商品都有无限个!
问背包能背的物品最大价值是多少?
第一步 确定 dp 数组以及下标的含义
dp[i][j] 表示从下标为 [0-i] 的物品,每个物品可以取无限次,放进容量为 j 的背包,价值总和最大是多少。
第二步 确定递推公式
- 不放物品 i:背包容量为 j,里面不放物品 i 的最大价值是 dp[i - 1][j]。
- 放物品 i:背包空出物品 i 的容量后,背包容量为 j - weight[i],dp[i][j - weight[i]] 为背包容量为 j - weight[i] 且不放物品 i 的最大价值,那么 dp[i][j - weight[i]] + value[i] (物品 i 的价值),就是背包放物品 i 得到的最大价值
递推公式:dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);
(注意,完全背包二维 dp 数组 和 01 背包二维 dp 数组 递推公式的区别,01 背包中是 dp[i - 1][j - weight[i]] + value[i]))
第三步 dp 数组如何初始化
首先从 dp[i][j] 的定义出发,如果背包容量 j 为 0 的话,即 dp[i][0],无论是选取哪些物品,背包价值总和一定为 0。
状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]); 可以看出有一个方向 i 是由 i-1 推导出来,那么 i 为 0 的时候就一定要初始化。
dp[0][j],即:存放编号 0 的物品的时候,各个容量的背包所能存放的最大价值。
那么很明显当 j < weight[0] 的时候,dp[0][j] 应该是 0,因为背包容量比编号 0 的物品重量还小。
当 j >= weight[0] 时,dp[0][j] 如果能放下 weight[0] 的话,就一直装,每一种物品有无限个。
第四步 确定遍历顺序
二维 dp:
既可以先遍历物品再遍历背包,也可以先遍历背包再遍历物品
第五步 举例推导 dp 数组
以本篇举例数据为例,填满了 dp 二维数组如图:

因为 物品 0 的性价比是最高的,而且 在完全背包中,每一类物品都有无限个,所以有无限个物品 0,既然物品 0 性价比最高,当然是优先放物品 0。
def ():
dp = [[] * (bag_weight + ) _ (n)]
j (weight[], bag_weight + ):
dp[][j] = dp[][j - weight[]] + value[]
i (, n):
j (bag_weight + ):
j < weight[i]:
dp[i][j] = dp[i - ][j]
:
dp[i][j] = (dp[i - ][j], dp[i][j - weight[i]] + value[i])
dp[n - ][bag_weight]
n, bag_weight = (, ().split())
weight = []
value = []
_ (n):
w, v = (, ().split())
weight.append(w)
value.append(v)
(knapsack(n, bag_weight, weight, value))

