標籤:
題目連結:
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=17434
動態規劃:
經典的01背包問題
有N件物品和一個容量為V的背包。第i件物品的費用是c[i],價值是w[i]。
求解將哪些物品裝入背包可使價值總和最大。
這是最基礎的背包問題,特點是:每種物品僅有一件,可以選擇放或不放。
用子問題定義狀態:即f[i][v]表示前i件物品恰放入一個容量為v的背包可以獲得的最大價值。則其狀態轉移方程便是:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
將它詳細解釋一下:
“將前i件物品放入容量為v的背包中”這個子問題,若只考慮第i件物品的策略(放或不放),那麼就可以轉化為一個只牽扯前i-1件物品的問題。
如果不放第i件物品,那麼問題就轉化為“前i-1件物 品放入容量為v的背包中”,價值為f[i-1][v];
如果放第i件物品,那麼問題就轉化為“前i-1件物品放入剩下的容量為v-c[i]的背包中”,此時能獲得的最大價值就是f[i-1][v-c[i]]再加上通過放入第i件物品獲得的價值w[i]。
先考慮上面講的基本思路如何?,肯定是有一個主迴圈i=1..N,每次算出來二維數組f[i][0..V]的所有值。
那麼,如果只用一個數組 f[0..V],能不能保證第i次迴圈結束後f[v]中表示的就是我們定義的狀態f[i][v]呢?
f[i][v]是由f[i-1][v]和f[i-1] [v-c[i]]兩個子問題遞推而來,能否保證在推f[i][v]時(也即在第i次主迴圈中推f[v]時)能夠得到f[i-1][v]和f[i-1] [v-c[i]]的值呢?
事實上,這要求在每次主迴圈中我們以v=V..0的順序推f[v],這樣才能保證推f[v]時f[v-c[i]]儲存的是狀態 f[i-1][v-c[i]]的值。
虛擬碼如下:
for i=1..N
for v=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
其中的f[v]=max{f[v],f[v-c[i]]}一句恰就相當於我們的轉移方程f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]},因為現在的f[v-c[i]]就相當於原來的f[i-1][v-c[i]]。
最後的f[v]就是問題的最優解了。
1 #include<cstdio> 2 #include<cstring> 3 const int MAXL = 1000+5; 4 int max(int a, int b){ return a>b ? a : b;} 5 int t, n, v, value[MAXL], volume[MAXL], dp[MAXL]; 6 7 int main(){ 8 scanf("%d", &t); 9 while(t--){10 scanf("%d%d", &n, &v);11 for(int i = 1; i <= n; i++) scanf("%d", &value[i]);12 for(int i = 1; i <= n; i++) scanf("%d", &volume[i]);13 memset(dp, 0, sizeof(dp));14 for(int i = 1; i <= n; i++){15 for(int j = v; j >= volume[i]; j--){ // 注意j的停止條件,一是防止越界,二是小於0後的答案就是dp[j],已經計算過了16 //從v遍曆至0的逆序可以保證dp[j-volume[i]]是上一層的計算結果,17 //又因為dp數組第二維的v只涉及到上一層的資料,其他層的資料都不會再訪問18 //從而省去dp數組的第一維度,每次都對之前的資料進行重新整理,節省空間的複雜度至O(n);19 dp[j] = max(dp[j], dp[j-volume[i]]+value[i]);20 }21 }22 printf("%d\n", dp[v]);23 }24 25 return 0;26 }
HDOJ 2602 Bone Collector 【動態規劃】