1. 程式人生 > >由DAG到背包問題——記憶化搜索和遞推兩種解法

由DAG到背包問題——記憶化搜索和遞推兩種解法

必須 結果 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到背包問題——記憶化搜索和遞推兩種解法