原創
問題描述:
問題描述:
給定N個物品,每個物品有一個重量W和一個價值V.你有一個能裝M重量的背包.問怎么裝使得所裝價值最大.每個物品只有一個.
輸入格式
輸入的第一行包含兩個整數n, m,分別表示物品的個數和背包能裝重量。
以后N行每行兩個數Wi和Vi,表示物品的重量和價值
以后N行每行兩個數Wi和Vi,表示物品的重量和價值
輸出格式
輸出1行,包含一個整數,表示最大價值。
樣例輸入
3 5
2 3
3 5
4 7
2 3
3 5
4 7
樣例輸出
8
數據規模和約定
1<=N<=200,M<=5000.
解題思路:
大家應該都看過很多大神的代碼了,如果還有不明白的地方,不妨聽聽我的解釋,希望能給你帶來柳暗花明的感覺。
首先大家都應該知道,一個物品,有放與不放兩種選擇,我們的最終結果就是綜合所有物品放與不放的選擇來確定。
有這樣一個狀態方程:
F[ i ][ j ]=max( F[ i-1 ][ j ] ,F[ i-1 ][ j - wi[i] ] + vi[ i ] );
其實不難理解,F[ i ][ j ] 代表 當放到第 i 個物品時,此時容量還剩 j ,我們可以選擇放或者不放;
不放的話 F[ i ][ j ]=F[ i-1 ][ j ];就是相當於放了前 i-1 個物品中的物品;注意,不一定是前 i-1 個
物品全都放進了背包。第 i 個物品放進去的話 F[ i ][ j ]=F[ i-1 ][ j - wi[i] ] + vi[ i ]; 放進去了所以
在前面放了 i-1 個物品中的物品的基礎上,容量減去 wi[i], 價值加上vi[ i ](但得在wi[i] <= j 條件下);
有些同學估計挺納悶,在放得下的前提下從(F[ i-1 ][ j ] ,F[ i-1 ][ j - wi[i] ] + vi[ i ] ) 這兩個中選擇
最大的,那肯定是加入了第 i 個物品的大呀,這還需要思考嗎?請看下圖:

假設有3個物品,重量分別為:9、7、8;價值分別是:10、15、16,背包容量是20.
其實只要我們按照那個公式套進去,就會發現,每個物品放與不放的情況都會被遍歷到,
回到上面的問題,F[ i-1 ][ j ] < F[ i-1 ][ j - wi[i] ] + vi[ i ] 是不錯,但是你放了第 i 個物品,假設是這個例子
的最后一個物品,前面你或許就得少放一個物品了。也許你放了第 i 個物品,放不下 i+1 個物品了,但是 vi[ i+1 ] > vi[ i ]
所以每種情況我們都要遍歷過,選出最優的。

大家請看上圖,思路就是從左上角開始,一行一行的求出所有放與不放的可能,右下角的44就是我們要求的答案了;但是需要注意的是,
其實很多數據我們根本用不着,從最后一行往上數,我們要的數據個數只是1、2、4、8、16個而已。
代碼:

1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 5 int max(int a,int b) 6 { 7 return a>b?a:b; 8 } 9 10 int main() 11 { 12 int n,m; 13 scanf("%d%d",&n,&m); 14 15 int *w,*v; 16 w=(int *)malloc(sizeof(int)*n); 17 v=(int *)malloc(sizeof(int)*n); 18 19 int **arr; //動態分配二維數組 20 arr=(int **)malloc(sizeof(int *)*(n+1)); 21 int i; 22 for(i=0;i<=n;i++) 23 arr[i]=(int *)malloc(sizeof(int)*(m+1)); 24 25 int j; 26 for(i=0;i<=n-1;i++) 27 scanf("%d%d",&w[i],&v[i]); 28 for(i=0;i<=n;i++) 29 for(j=0;j<=m;j++) 30 { 31 if(i==0 || j==0) //0容量或者0物品置0 32 arr[i][j]=0; 33 else if(w[i-1]<=j) 34 arr[i][j]=max(arr[i-1][j],arr[i-1][j-w[i-1]]+v[i-1]); 35 else 36 arr[i][j]=arr[i-1][j]; 37 } 38 printf("%d",arr[n][m]); 39 free(arr); 40 free(w); 41 free(v); 42 return 0; 43 }
思路提升:
從上面的表格可以看到,我們用了一個二維數組存儲了每一行的數據,但是實際情況是第1行數據只是第2行用,用完以后就沒用了,
第 i 行的數據只是第 i+1 行使用,用完了就放在數組里面占用大量空間了;所以我們完全可以用一個 “滾動數組” 來實現這種操作,
所謂的滾動數組其實很簡單,就是數組里面我們先存放第 1 行的數據,然后我們根據第 1 行的數據求出第 2 行的數據存放在數組;
就是第 i 行的數據先存放,我求出第 i+1 行的數據以后數組我來占用,這樣不斷變化的數組就是滾動數組,很簡單吧。
代碼實現:

1 #include<stdio.h> 2 #include<stdlib.h> 3 4 int max(int a,int b) 5 { 6 return a>b?a:b; 7 } 8 9 int main() 10 { 11 int n,m; 12 scanf("%d%d",&n,&m); 13 14 int *wi,*vi; 15 wi=(int *)malloc(sizeof(int)*(n+1)); 16 vi=(int *)malloc(sizeof(int)*(n+1)); 17 18 int i; 19 for(i=1;i<=n;i++) 20 scanf("%d%d",&wi[i],&vi[i]); 21 22 int *dp; 23 dp=(int *)malloc(sizeof(int)*(m+1)); //滾動數組 24 for(i=0;i<=m;i++) 25 dp[i]=0; 26 27 int j; 28 for(i=0;i<=n;i++) 29 for(j=m;j>=wi[i];j--) //容量從大到小 30 { 31 if(i==0) 32 { 33 dp[j]=0; 34 continue; 35 } 36 37 dp[j]=max(dp[j],dp[j-wi[i]]+vi[i]); 38 } 39 40 printf("%d",dp[m]); 41 return 0; 42 }
代碼中狀態方程變為:dp[j]=max(dp[j],dp[j-wi[i]]+vi[i]);
請大家注意,dp[j]代表的是現在要求的第 i 個物品考慮放不放在容量為 j 的物品下的最優解,而括號里面的dp[j]代表的
是放 i-1 個物品中的物品時的最優解,相當於上面的F[ i-1 ][ j ],也就是說這個是前一個狀態的量,而dp[j-wi[i]]+vi[i]
就是上面的F[ i-1 ][ j - wi[i] ] + vi[ i ]了。但是容量 j 的值要從大到小了,因為我們求大容量背包的價值時要用到
小容量背包的價值,所以如果從小容量開始求,等到求放下一個物品時,從小容量開始求,那么當我們求這個物品的大容量時放與不放時
上一個物品的小容量值由於被這個物品的小容量值覆蓋,所以要從大容量開始求。
2018-04-06