价值:记录学习过程的思考,本身就是一场动态规划的前生,记忆化搜索。
🎉🎉🎉 有趣的事情:二维压缩为一维的过程,为了保证等价,需要动起来。比如滚动数组,比较有趣的是,顺序递推很多时候趋向于反转。
物品0、物品1、物品2、🎒
最近系统性的学习dp,对于背包模型,手写分析过程记录。
百科定义:背包问题 (Knapsack problem)是一种组合优化的 NP完全问题 。 问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。 问题的名称来源于如何选择最合适的物品放置于给定背包中。
// 【01背包递推公式】
// dp[j] = Max(dp[j], dp[j-weight[i]]+value[i]) 倒序
// dp[i][j] = Max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]) 顺序无限制
// 【完全背包递推公式】
// dp[j] = Max(dp[j], dp[j-weight[i]]+value[i]) 正序
// dp[i][j] = Max(dp[i-1][j],dp[ i ][j-weight[i]]+value[i]) 顺序无限制
01背包二维经典板子
for(int i=0; i<物品编号;i++)
for (int j = 0; j<=bagWeight ; j++)
//二纬递推公式
dp[i][j] = dp[i - 1][j];
if(j>= weight[i]) dp[i][j] = Max(dp[i][j],dp[i - 1][j - weight[i]] + value[i])
// 答案 在 dp[物品个数][背包总容量]
01背包一纬经典板子
//物品正序
for(int i=0; i<物品编号;i++)
//01背包剩余重量倒序
for (int j = bagWeight; j>= weight[i] ; j--)
//一纬递推公式
dp[j] = Max(dp[j] , dp[j - weight[i]] + value[i])
// 答案 在 dp[j]
完全背包二维经典板子
for(int i=0; i<物品编号;i++)
for (int j = 0; j<=bagWeight ; j++)
//二纬递推公式
dp[i][j] = dp[i-1][j];
if(j > weight[i]) dp[i][j] = Max(dp[i][j],dp[i][j - weight[i]] + value[i])
// 答案 在 dp[物品个数][背包总容量]
完全背包一维经典板子
//物品正序
for(int i=0; i<物品编号;i++)
//完全背包容量正序
for (int j = weight[i]; j<= bagWeight ; j++)
//一纬递推公式
dp[j] = Max(f[j] , dp[j - weight[i]] + value[i])
// 答案 在 dp[j]
01背包 过程分析
(编号 i )物品 0 1 2 (编号 j )背包 0 1 2 3 4
【二维数组】dp表示:
1.状态含义dp[i][j]:
在 0~i 个物品中任意选择,在背包容量 j 的情况下,物品组成的最大价值。
2.递推公式:
// dp[i][j] = Max (dp[i-1][j] ,
// dp[i-1][j-weight[i]] + value[i]
// )
3.初始化:
dp[i][0]:背包容量为0,最大价值0。
dp[0][j]:放第0号物品,背包容量0~j的背包所能存放的最大价值。
当j<weight[0]时,dp[0][j] = 0,因为背包容量比编号0物品重量还小,等于没装。
当j>=weight[0]时,dp[0][j] = value[0],因为背包容量足够放编号0物品,只放一次,后边大容量背包也是一个的价值。
4.遍历顺序:
先遍历物品,再遍历背包。
先遍历背包,再遍历物品。
这两个参数的遍历顺序在二维下可以反转。
【图示】其中对背包的遍历顺序,可以正序,也可以倒序。
优化为一纬:滚动数组
(编号 i )物品 0 1 2 (编号 j )背包 0 1 2 3 4
【一纬数组】dp表示
1.dp状态表示含义dp[j]:
容量为j的背包所背最大价值为dp[j]
2.递推公式。由二维等价变化,直接抄。
// dp[j] = Max(dp[j],dp[j - weight[i]] + value[i])
右边的 j 中存储的数据,其实是二维里边这一行的上一行的数据。
// 这里只看公式,
// dp[j] = dp[j] 是恒等式不用管,
// dp[j-weight[i]] 是比j小的一个数,
// 顺序:我们从大到小循环,此时 j-weight[i]还没有被更新过(从右到左,右边先更新,左边还没有更新),还是上一层的[j-weight[i]],只不过现在显示在一维中
// 所以只要从大到小循环,就可以保证这两个方程式是等价的。
3.初始化。
如果背包为0,背包最大价值为0 别的非0的j怎么整?
分类讨论: 负数,不是背包问题,不讨论。 正数,
根据dp公式,如果他和本身取最大值, 他和本身取最大值时,我们应该初始化为非0里边的最小值, 这样,初始化后,不会因为初始化很大的值,覆盖了我们在递推公式中本来应该计算的值。
4.遍历顺序,一定要倒序背包。保证只仅仅进行公式推导下,满足只添加一次某个物品。
【图示】
完全背包手动图示理解
状态表示 集合含义 属性 状态计算 集合划分 一般地集合的方案数目求max 所有f(i,j),所有划分集合求和:所有max求和
for(int i = 0 ; i < 物品个数; i++){
//板子
for(int j = v[i]; j<=m;j++){
f[j] = Max(f[j],f[j-v[i]] + w[i]);
}
//朴素推导过程
//朴素想法,转成01,枚举求max
for(int j = W; j>=v[i];j--){
for(int k =0 ; k * v[i] <=j ; k++){
f[j] = Max(f[j],f[j - k * v[i]] + k * w[i]);
}
}
}
【图示】