背包问题

 

一、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背包整体保持一致。不同的只是状态更新时的遍历顺序。

原文地址:https://www.cnblogs.com/shenckicc/p/6806209.html