01背包总结
背包可以不装满
题干:
Weight[N] , Value[N], backSize
解释:
有N件物品,重量存放在Weight[]数组中,价值存放在Value[]数组中。即第 i i i件物品对应的重量为 W e i g h t [ i ] Weight[i] Weight[i] ,价值为 V a l u e [ i ] Value[i] Value[i]
在背包最多承受的重量为 b a c k S i z e backSize backSize 且 每件物品最多使用一次的情况下, 背包的最大价值是多少。
二维dp状态转移方程:
d p [ i ] [ k ] = max { d p [ i − 1 ] [ k ] ( 前 i − 1 件物品放入背包 ) d p [ i − 1 ] [ k − W e i g h t [ i ] ] + V a l u e [ i ] ( 前 i 件物品放入背包 ) dp[i][k] = \max{\begin{cases}dp[i-1][k]& (前i-1件物品放入背包)\\dp[i-1][k-Weight[i]]+Value[i] & (前i件物品放入背包)\end{cases}} dp[i][k]=max{dp[i−1][k]dp[i−1][k−Weight[i]]+Value[i](前i−1件物品放入背包)(前i件物品放入背包) 01背包二维状态转移方程
解释:
乍一看方程会有些懵,我来解释一下 d p [ i ] [ k ] dp[i][k] dp[i][k]的含义
i : 将前 i 件物品放入背包 i : 将前i件物品放入背包 i:将前i件物品放入背包
k : 此时背包的容量为 k k : 此时背包的容量为k k:此时背包的容量为k
d p [ i ] [ k ] : 前 i 件物品在背包最大重量为 k 的情况下的最大价值 dp[i][k]:前i件物品在背包最大重量为k的情况下的最大价值 dp[i][k]:前i件物品在背包最大重量为k的情况下的最大价值
接着看两个选项
$dp[i-1][k] : $ 根据上面的解释 不难带入理解:前(i-1)件物品在背包最大重量为k时的最大价值
d p [ i − 1 ] [ k − W e i g h t [ i ] ] + V a l u e [ i ] : dp[i-1][k-Weight[i]]+Value[i] : dp[i−1][k−Weight[i]]+Value[i]: Value[i] 是 选择了将第 i 件物品放入背包, 那么此时背包最多还能放 (k-Weight[i])这么重的物品,而这些物品我们需要在前(i-1)件物品中选择
那么能不能优化一下空间呢?当然可以。
优化二维dp:
在上面的例子中,每一行只跟上一行有关系,那么是否可以使用一维数组达到二维数组的效果呢?
哈哈答案当然是可以的。
假设 d p dp dp 数组是一个长度为 N + 1 N+1 N+1 的一维数组(可以将这里的 d p dp dp数组想象成上面二维数组 d p [ i ] [ k ] dp[i][k] dp[i][k] 中的任意一行)
我们呢知道:在更新 d p [ m ] [ n ] dp[m][n] dp[m][n]所在的一行时,在二维 d p [ i ] [ k ] dp[i][k] dp[i][k]中,与这行相关的只有 d p [ m − 1 ] [ n ] dp[m-1][n] dp[m−1][n]所在的这一行,也就是 d p [ m ] [ n ] dp[m][n] dp[m][n] 的上一行。
具体见 01背包二维状态转移方程
所以 使用一维 d p [ i ] dp[i] dp[i] 来计算是完全可能的
那么 此时 如何解释 d p [ i ] dp[i] dp[i] 呢?
i : i: i: 背包最大重量为 i i i
d p [ i ] : dp[i]: dp[i]: 在背包最大重量为 i i i 的情况下的最大价值
一维dp状态转移方程:
d p [ k ] = max { d p [ k ] ( 在不取第 i 件物品情况下的价值 ) V a l u e [ i ] + d p [ k − W e i g h t [ i ] ] ( 在取第 i 件物品情况下的价值 ) dp[k] = \max{\begin{cases}dp[k]&(在不取第 i 件物品情况下的价值)\\Value[i]+dp[k-Weight[i]] & (在取第i件物品情况下的价值)\end{cases}} dp[k]=max{dp[k]Value[i]+dp[k−Weight[i]](在不取第i件物品情况下的价值)(在取第i件物品情况下的价值) 01背包一维状态转移方程
一维dp 的重点在于遍历背包容量时的顺序
我们来看一个例子
std::vector<int> Weight = {2,2};
std::vector<int> Value = {3,5};
std::vector<int> dp(7,0);
遍历第一个物品
我们希望得到的 d p dp dp 数组
dp[0] | dp[1] | dp[2] | dp[3] | dp[4] | dp[5] | dp[6] |
---|---|---|---|---|---|---|
0 | 0 | 3 | 3 | 3 | 3 | 3 |
遍历第二个物品
我们希望得到的 d p dp dp 数组
dp[0] | dp[1] | dp[2] | dp[3] | dp[4] | dp[5] | dp[6] |
---|---|---|---|---|---|---|
0 | 0 | 5 | 5 | 8 | 8 | 8 |
假设我们已经遍历完了第一个物品 请记住现在 d p dp dp 数组 的状态
此时 我们开始遍历第二个物品对应的 d p dp dp
遍历方式:
这时候我们有两种遍历方式 1. 从 d p [ 0 ] → d p [ 6 ] 从 dp[0]\to dp[6] 从dp[0]→dp[6] 2. 从 d p [ 6 ] → d p [ 0 ] 从 dp[6]\to dp[0] 从dp[6]→dp[0]
我们来看一下有什么区别
注意: 下面是 遍历第二件物品的情况
- 从 d p [ 0 ] → d p [ 6 ] 从 dp[0]\to dp[6] 从dp[0]→dp[6] 即 k k k 从 0 → \to → 6 ,得到下面的 d p dp dp数组
dp[0] | dp[1] | dp[2] | dp[3] | dp[4] | dp[5] | dp[6] |
---|---|---|---|---|---|---|
0 | 0 | 5 | 5 | 10 | 10 | 10 |
- 从 d p [ 6 ] → d p [ 0 ] 从 dp[6]\to dp[0] 从dp[6]→dp[0]即 k k k 从 6 → \to → 0 ,得到下面的 d p dp dp数组
dp[0] | dp[1] | dp[2] | dp[3] | dp[4] | dp[5] | dp[6] |
---|---|---|---|---|---|---|
0 | 0 | 5 | 5 | 8 | 8 | 8 |
发现按照 从 d p [ 0 ] → d p [ 6 ] 从 dp[0]\to dp[6] 从dp[0]→dp[6] 这种遍历背包容量的方式得到的结果并不正确
分析一下原因:
在遍历时,我们覆盖了背包某一容量的值 ,而这一容量值在更新之后的值时需要使用,错误的值导致了错误的结果。
即 过早更新了 d p [ k − W e i g h t [ i ] ] dp[k-Weight[i]] dp[k−Weight[i]] 导致 了错误。
在上面的例子中 我们过早更新了 d p [ 2 ] dp[2] dp[2] 的值 ,之后我们就丢失了 上一件物品 的数据,之后在更新 d p [ 4 ] 、 d p [ 5 ] 、 d p [ 6 ] dp[4]、dp[5]、dp[6] dp[4]、dp[5]、dp[6] 时,都需要比较 d p [ 2 ] dp[2] dp[2] 的值。但是此时 d p [ 2 ] dp[2] dp[2]已经是本层的最大价值了,应该比较的是上一层的最大价值,这就导致了价值错误。
所以为了保证在更新较大背包容量的最大价值时,使用正确的数据,应该从背包容量较大处开始比较。
即采用 从 d p [ n ] → d p [ 0 ] 从 dp[n]\to dp[0] 从dp[n]→dp[0]
那么 dp[n] 即为所求。
恰好装满:
如果要求是恰好装满,只需在初始化 d p dp dp数组时,将 d p [ 0 ] dp[0] dp[0] 初始化为 0,其余初始化为 I N T _ M I N INT\_MIN INT_MIN
std::vector<int> dp(n+1,INT_MIN);
dp[0] = 0;