背包问题是动态规划中的一种经典题型, 背包问题的变体繁多且复杂,这里总结一下背包问题中的0-1背包、完全背包以及多重背包三类问题。
描述:有n件物品,每件物品的重量为w[ i ],价值为v[ i ],现在有容量为m的背包,问如何选择物品使得装入背包的物品价值总量最大。
对于这种问题,我们首先想到的就是遍历所有的情况,然后找到其中价值总量最大的,但是这个方法的时间复杂度为O(2n),复杂度太高了,因此需要用动态规划来求解该类问题,则可以使得时间复杂度降为O(nm)。
首先设置一个二维数组dp[][],其中dp[ i ][ j ]代表前 i 件物品装入容量为 j 的背包中价值总量最大值,则dp[ n ][ m ]即为所求问题的解。
对于第 i 件物品,我们有两种处理方法,即放或者不放:
所以由上面的分析可知,状态转移方程即为:
dp[ i ][ j ] = max(dp[ i - 1 ][ j ], dp[ i - 1 ][ j - w[ i ] ] + v [ i ])
对于边界条件,当容量为0或者物品数量为0时,最大价值量为0,即dp[ i ][ j ] = 0( i == 0 || j == 0)
上述二维数组dp的行代表的是更新的次数,因此我们其实可以将其转换为一维数组,优化后的状态转移方程为:
dp[ j ] = max(dp[ j ], dp[ j - w[ i ] ] + v [ i ])
但是由于涉及到两种更新的状态,即等式右边的dp都是上一次更新的结果,而左边则是这次更新的结果,因此我们需要采用逆序遍历所有的 j 的方法,这样就能保证在更新dp[ j ]的时候,dp[ j - w[ i ] ]没有被更新。
参考如下几篇博客:
【牛客网】KY66 点菜问题
【牛客网】KY75 采药
【牛客网】KY14 最小邮票数
完全背包问题与0-1背包问题的区别就在于,前者每件物品的数量是无限个,而后者只有一个。
同样的,首先设置一个二维数组dp[][],其中dp[ i ][ j ]代表前 i 件物品装入容量为 j 的背包中价值总量最大值,则dp[ n ][ m ]即为所求问题的解。
对于第 i 件物品,我们有两种处理方法,即放或者不放:
所以由上面的分析可知,状态转移方程即为:
dp[ i ][ j ] = max(dp[ i - 1 ][ j ], dp[ i ][ j - w[ i ] ] + v [ i ])
对于边界条件,当容量为0或者物品数量为0时,最大价值量为0,即dp[ i ][ j ] = 0( i == 0 || j == 0)
可以发现,完全背包问题与0-1背包问题的状态转移方程的区别就在于前者是dp[ i ][ j - w[ i ] ]
,而后者则是dp[ i - 1 ][ j - w[ i ] ]
。
优化为一维数组的转移方程为:
dp[ j ] = max(dp[ j ], dp[ j - w[ i ] ] + v [ i ])
可以发现与0-1背包问题的是一样的,唯一不同的就是遍历的顺序不同,即0-1背包问题是逆序遍历 j ,保证第 j - w[ i ]是上一次更新的结果,而完全背包问题则是顺序遍历 j ,保证第 j - w[ i ]是这一次更新的结果
参考如下博客:
【POJ】1384 Piggy-Bank
多重背包问题是0-1背包问题与完全背包问题的一个折中,即每一件物品的数量不是只有一个,也不是无限个,而是k个,问题描述为:有n件物品,每件物品的重量为w[ i ],价值为v[ i ],数量为k[ i ],现在有容量为m的背包,问如何选择物品使得装入背包的物品价值总量最大。
对于这类问题,我们可以将其处理成0-1背包问题,即根据k将其分解,成多件物品,之后就转换为0-1背包问题了。我们可以将其分解为20,21,22,…,2c,k - 2c + 1,其中c为使得k - 2c + 1 >= 0的最大整数,之后的步骤就和0-1背包问题的求解步骤一样。
参考如下博客:
【AcWing】4. 多重背包问题 I