背包问题
一、01背包问题
最基础的背包问题:有N件物品和一个容量为V的背包。第i件物品的体积是w[i],价值是v[i]。求解将哪些物品装入背包可使物品的总价值达到最大。
我们用dp[i][j]表示在总体积不超过j的情况下,前i个物品所能达到的最大价值。初始时,dp[0][j]为0。
依据每种物品是否被放入背包,每个状态有两个状态转移的来源。
1.若物品i被放入背包,设其体积为w,价值为v,则dp[i][j] = dp[i-1][j-w]+v。
2.若物品不放入背包,则dp[i][j] = dp[i-1][j]。
选择两者之中的较大值成为状态dp[i][j]的值。 即状态转移方程:dp[i][j] = max{ dp[i-1][j], dp[i-1][j-w]+v};
同时注意:j-w的值是否为非负值,若为负值则该状态来源不能被转移。
1 // 状态转移
for(int i=1; i<=m; i++) 2 {//循环每一个物品 3 4 for(int j=t; j>=list[i].w; j--) 5 { 6 dp[i][j]=max(dp[i-1][j], dp[i-1][j-list[i].w]+list[i].v); 7 } 8 //j属于list[i].w-1~0 9 for(int j=list[i].w-1; j>=0; j--) 10 { 11 dp[i][j]=dp[i-1][j]; 12 } 13 }
观察状态转移的特点,我们发现dp[i][j]的转移仅与dp[i-1][j-list[i].w]和dp[i-1][j]有关,即仅与二维数组中本行的上一行有关。根据这个特点,我们可以将原本的二维优化。
dp[j] = max{ dp[j], dp[j-list[i].w]+v };
1 // 状态转移 (简化后) 2 for(int i=1; i<=m; i++) 3 {//循环每一个物品 4 5 for(int j=t; j>=list[i].w; j--) 6 { 7 dp[j]=max(dp[j], dp[j-list[i].w]+list[i].v); 8 } 9 10 }
在0-1背包中,之所以逆序循环更新状态是为了保证更新dp[j]时,dp[j-list[i].w]的状态尚未因为本次更新而发生变化。因此必须逆序更新每个dp[j]的值。
0-1背包存在一个简单的变化:即要求所选择的物品必须恰好装满背包。此时,只需要改变初始状态,dp[0][0]为0,而其他dp[0][j]值均变化为负无穷或者是不存在。该变化与原始的0-1背包的差别只体现在初始值方面。
二、完全背包
完全背包是在0-1背包的基础上,使每种物品的数量无限增加。
完全背包:有一个体积为V的背包,同时又n个物品,每个物品均有各自的体积w和价值v,每个物品的数量均为无限个,求使用该背包最多能装的物品价值总和。
简单来说,使用之前空间优化过的一维数组来解,按如下方法转移:
1 // 状态转移 (简化后) 2 for(int i=1; i<=m; i++) 3 {//循环每一个物品 4 5 for(int j=list[i].w; j<=t; j++) 6 { 7 dp[j]=max(dp[j], dp[j-list[i].w]+list[i].v); 8 } 9 10 }
与0-1背包相比,似乎只存在着对状态j的遍历顺序有所差异。这是因为0-1背包中每个物品至多只能被选择一次。而在完全背包中,每个物品可以被选择无限次,那么状态dp[i][j]恰好可以由可能已经放入物品i的状态转移而来。
总结一下:完全背包解法与0-1背包整体保持一致。不同的只是状态更新时的遍历顺序。