01 背包¶
有 n
件物品和一个最多能背重量为 w
的背包。第 i
件物品的重量是 weight[i]
,得到的价值是 value[i]
。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
例如,背包最大重量为 4,物品为
重量 | 价值 | |
---|---|---|
物品 0 | 1 | 15 |
物品 1 | 3 | 20 |
物品 2 | 4 | 30 |
暴力¶
每个物品只有选和不选两种状态,可以暴力枚举所有情况,时间复杂度为 \(O(2^n)\)。
二维 dp¶
用 dp[i][j]
表示:从物品 0
到物品 i
中选取,放入容量为 j
的背包中,能得到的最大价值。
dp[i][j] | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
0 | 0 | 15 | 15 | 15 | 15 |
1 | 0 | 15 | 15 | 20 | 35 |
2 | 0 | 15 | 15 | 20 | 35 |
当背包容量为 j
时,对于第 i
个物品,只有选和不选两种情况
- 如果不选,总价值为
dp[i-1][j]
- 如果选,则先放其他物品并空出
weight[i]
,最后再放物品i
,总价值为dp[i-1][j-weight[i]] + value[i]
总结一下,状态转移方程为
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]);
dp[i][j]
依赖上一行 dp[i-1][]
的值,所以 dp
表的第一行必须手动初始化。
// j < weight[0] 时 dp[0][j] 都是 0
for (int j = weight[0]; j <= bagweight; j++)
{
dp[0][j] = value[0];
}
完整代码
vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
// j < weight[0] 已在上方被初始化为 0
for (int j = weight[0]; j <= bagweight; j++)
{
dp[0][j] = value[0];
}
for (int i = 1; i < weight.size(); i++) // 物品,从 1 开始
{
for (int j = 0; j <= bagweight; j++) // 遍历背包容量,注意 j 的范围
{
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
最终结果是 dp[n-1][bagweight]
。
一维 dp¶
注意到状态转移方程中 dp[i][]
只和上一行 dp[i-1][]
有关,所以可用滚动数组优化。
dp[j] = max(dp[j], dp[j-weight[i]] + value[i]);
dp
数组要根据题意初始化。前面举的例子里,所有物品的价值都是正的,一开始什么物品都没放,所以 dp
数组初始化为 0
。
vector<int> dp(bagweight + 1, 0);
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]);
}
}
最终结果是 dp.back()
。还有几点要注意:
- 因为
dp[j]
依赖前面的元素dp[j-weight[i]]
,所以j
这个维度(背包容量)要从大到小遍历。 - 两个
for
循环不能交换。因为j
是从大到小遍历的,如果交换两个for
循环的话,dp[j-weight[i]]
总是初始值0
,状态转移方程就变成dp[j] = max(dp[j], value[i])
,导致背包中至多只有一个物品。
参考¶
相关文章