01背包详解

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[i1][k]dp[i1][kWeight[i]]+Value[i](i1件物品放入背包)(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[i1][kWeight[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[m1][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[kWeight[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]
0033333

遍历第二个物品

我们希望得到的 d p dp dp 数组

dp[0]dp[1]dp[2]dp[3]dp[4]dp[5]dp[6]
0055888

假设我们已经遍历完了第一个物品 请记住现在 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]

我们来看一下有什么区别

注意: 下面是 遍历第二件物品的情况

  1. 从 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]
0055101010
  1. 从 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]
0055888

发现按照 从 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[kWeight[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;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值