背包問題是動態規划中的經典問題,而01背包問題是最基本的背包問題,也是最需要深刻理解的,否則何談復雜的背包問題。
POJ3624是一道純粹的01背包問題,在此,加入新的要求:輸出放入物品的方案。
我們的數組基於這樣一種假設:
totalN表示物品的種類,totalW表示背包的容量
w[i]表示第i件物品的重量,d[i]表示第i件物品的價值。
F(i,j)表示前i件物品放入容量為j的背包中,背包內物品的最大價值。
F(i,j) = max{ F(i-1,j) , F(i-1,j-w[i])+d[i] }
我們僅考慮第i件物品到底放不放進背包
第一項表示不放入背包的情況。
第二項表示放入背包的情況。
然后,我們為了獲得具體的方案,每當在局部最優的方案成立時,則將path[i][j]置為1,當求的最優結果后,從最終結果開始回溯,看看在第i輪的局部最優中是否放入物品i。
此時的代碼如下所示:
#include <stdio.h> #include <string.h> #include <string> #include <iostream> using namespace std; #define max(a,b) a>b?a:b; //數組要設的比給的范圍稍大一些 int dp[3410][12900]; int path[3410][12900]; int w[3410]; int d[3410]; int totalN; int totalW; int main() { int i,j; scanf("%d",&totalN); scanf("%d",&totalW); for(i=1;i<=totalN;i++) { scanf("%d",&w[i]); scanf("%d",&d[i]); } memset(dp,0,sizeof(dp)); for(i=1;i<=totalN;i++){ for(j=1;j<=totalW;j++){ /*必不可少,需要根據上一輪狀態, 而如果直接從j=w[i]開始,會使0<j<w[i]都為0,影響正確的結果。 在之后優化的1維矩陣方法中,就不會存在這種問題, 由於其逆序遍歷,上一輪的結果只要不覆蓋,就一直存在*/ dp[i][j] = dp[i-1][j]; if(j>=w[i] && dp[i][j]<dp[i-1][j-w[i]]+d[i]){ dp[i][j] =dp[i-1][j-w[i]]+d[i]; path[i][j] = 1; } } } printf("背包內物品的最大價值是:\n"); printf("%d\n",dp[totalN][totalW]); i = totalN; j = totalW; printf("放入背包的物品是:\n"); while(i>0 && j>0) { if(path[i][j] == 1) { printf("%d\n",d[i]); j = j - w[i]; } i = i - 1; } system("pause"); return 0; }
此時的時間復雜度是O(NW),已經無法優化,但空間復雜度可以由O(NW)下降到O(W).。
我們可以看到F(i,j)僅僅依賴於F(i-1,j)和F(i-1,j-w[i])+d[i],也就是說,我們需要依賴的上一輪的序列中的元素的坐標沒有在J的右邊的,此時我們就可以使用逆序遍歷即可,直接應用上一輪的數據。
這樣的逆序遍歷還有一層意義,即每個物品只放入一次,因為所以來的兩項都是沒有放入過第i件物品的情況,從不依賴放入第i件物品的情況。這點需要好好的理解一下,有助於后續對完全背包問題的理解。
上代碼:
#include <stdio.h> #include <string.h> #include <string> #include <iostream> using namespace std; //數組要設的比給的范圍稍大一些 int bag[12900]; int path[3410][12900]; int w[3410]; int d[3410]; int totalN; int totalW; int main() { int i,j; scanf("%d",&totalN); scanf("%d",&totalW); for(i=1;i<=totalN;i++) { scanf("%d",&w[i]); scanf("%d",&d[i]); } memset(bag,0,sizeof(bag)); for(i=1;i<=totalN;i++){ for(j=totalW;j>=w[i];j--){ if(bag[j]<bag[j-w[i]]+d[i]){ bag[j] = bag[j-w[i]]+d[i]; path[i][j]=1; } } } printf("背包內物品的最大價值是:\n"); printf("%d\n",bag[totalW]); i = totalN; j = totalW; printf("放入背包的物品是:"); while(i>0 && j>0) { if(path[i][j] == 1) { printf("%d\n",d[i]); j = j - w[i]; } i = i - 1; } system("pause"); return 0; }
最后如果想去AC的話,要采用第二種方案,降低空間復雜度,AC代碼(折疊):

#include <stdio.h> #include <string.h> #include <string> #include <iostream> using namespace std; //數組要設的比給的范圍稍大一些 int bag[12900]; int w[3410]; int d[3410]; int totalN; int totalW; int main() { int i,j; scanf("%d",&totalN); scanf("%d",&totalW); for(i=1;i<=totalN;i++) { scanf("%d",&w[i]); scanf("%d",&d[i]); } memset(bag,0,sizeof(bag)); for(i=1;i<=totalN;i++){ for(j=totalW;j>=w[i];j--){ if(bag[j]<bag[j-w[i]]+d[i]){ bag[j] = bag[j-w[i]]+d[i]; } } } printf("%d\n",bag[totalW]); system("pause"); return 0; }