由DAG到背包問題——記憶化搜索和遞推兩種解法
阿新 • • 發佈:2018-08-09
必須 結果 stream size 遞推 algo main style 得到
一、問題描述
物品無限的背包問題:有n種物品,每種均有無窮多個。第 i 種物品的體積為Vi,重量為Wi。選一些物品裝到一個容量為 C 的背包中,求使得背包內物品總體積不超過C的前提下重量的最大值。1≤n≤100, 1≤Vi≤C≤10000, 1≤Wi≤1000000.
二、解題思路
我們可以先求體積恰好為 i 時的最大重量(設為d[i]),然後取d[i]中的最大值(i ≤ C)。與之前硬幣問題,“面值恰好為S”就類似了。只不過加了新屬性——重量,相當於把原來的無權圖改成帶權圖,即把“+1”變成“+W[j]”。這樣,問題就變成了求以C為起點、終點任意的,邊權之和最大的路徑。
三、代碼實現
1、記憶化搜索
之前糾結這種方法的時間復雜度,先給結果:O(maxn * maxc)。因為計算dp(s)時,如果dp[i]中i是從0-->C,
則dp[i] = max(dp[i],dp[i - V[j]] + W[j]),dp[i - V[j]]已經計算出來且保存,相當於得到dp[i]沒有花費時間。如果dp[i]中i是從C-->0,
每次計算的都被保存且只計算一次,有幾次小的遞歸,也相當於沒有花費時間。
1 #include<stdio.h> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 7 const int INF = 0x3f3f3f3f; 8 const int maxn = 100 + 10; 9 const int maxc = 10000 + 10; 10 int n,V[maxn],W[maxn],C; 11 int d[maxc]; //d[i]表示總體積恰好為i時的最大重量 12 13 int dp(int s) 14 { 15 int& ans = d[s]; 16 if (ans != -1) return ans; 17 ans = - INF; 18 for (int i = 0; i < n; i++) 19 { 20 if (s >= V[i]) ans = max(ans, dp(s - V[i]) + W[i]);21 } 22 return ans; 23 } 24 void slove() 25 { 26 memset(d, -1, sizeof(d)); 27 d[0] = 0; 28 int res = -1; 29 for (int i = 0; i <= C; i++) 30 res = max(res, dp(i)); 31 printf("%d\n", res); 32 } 33 34 int main() 35 { 36 while (scanf("%d",&n) == 1 && n) 37 { 38 scanf("%d", &C); 39 for (int i = 0; i < n; i++) 40 scanf("%d%d", &V[i], &W[i]); 41 42 slove(); 43 } 44 return 0; 45 }
2、遞推式
這種寫法時間復雜度十分顯然,與記憶化搜索相同,都是O(maxn * maxc)。但必須註意循環的順序,比如容量只能從0-->C,而不能反過來,前一種寫法則沒有循環的順序要求。
1 void slove() 2 { 3 fill(d, d + n, -INF); 4 d[0] = 0; 5 int res = -1; 6 for (int i = 0; i <= C; i++) //容量的循環順序只能是從小到大 7 { 8 for (int j = 0; j < n; j++) 9 { 10 if(i >= V[j]) d[i] = max(d[i], d[i - V[j]] + W[j]); 11 } 12 res = max(res, d[i]); 13 } 14 printf("%d\n", res); 15 }
3、兩者比較
在得到狀態轉移方程之後,還需要思考如何編寫程序。盡管在很多情況下,記憶化搜索程序更直觀、易懂,但在0-1背包中遞推法更理想。因為已知狀態轉移方程後,遞推法的難點是循環順序,而有了“階段”定義後,循環順序變得十分顯然。
由DAG到背包問題——記憶化搜索和遞推兩種解法