1. 程式人生 > >01背包問題與動態規劃(DP)

01背包問題與動態規劃(DP)

clas const 解法 自己 沒有 end ostream 初始化 動手

技術分享圖片

解法一:我們先用最樸素的方法,著眼於每個物體是否進入背包,進行遍歷。

代碼如下:

#include<iostream>
#include<algorithm>
using namespace std;
int N,W;
const int maxn=105;
int v[maxn],w[maxn];
int rec(int i,int j){//從第i個下標開始,計算剩余j重量怎麽挑選商品 
    int ans;
    if(i==N) ans=0;//已經沒有商品可以選擇 
    else if(j<w[i]) ans=rec(i+1
,j);//如果背包容量裝不下下標為i的商品 else ans=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]); return ans; } int main(){ cin>>N>>W; for(int i=0;i<N;i++) cin>>w[i]; for(int i=0;i<N;i++) cin>>v[i]; int res=rec(0,W); cout<<res<<endl; return 0; }

然而這種算法是對每個商品都進行處理,每一層搜索都有兩個分支,時間復雜度為O(2^n),當n比較大的時候就會花費較多的時間。我們註意到,對每個商品進行搜索的時候,有時會出現相同的參數,

於是第二次調用的時候我們其實已經計算過一次了,等於是白白浪費了時間。所以我們有了新的想法:把第一次計算的結果給記錄下來,這樣可以省下不少時間。

於是有了解法二:

用一個二維數組記錄下之前第一次計算出的結果,等到第二次調用相同參數函數的時候,就不必計算。

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstring>
 4 using namespace std;
 5 const int maxN=3405;
 6 const int maxW=405;
 7 int dp[maxN][maxW];
8 int N,W; 9 int w[maxW],v[maxN]; 10 int rec(int i,int j){ 11 if(dp[i][j]>=0) return dp[i][j]; 12 int ans; 13 if(i==N) ans=0; 14 else if(j<w[i]) ans=rec(i+1,j); 15 else ans=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]); 16 return dp[i][j]=ans; 17 } 18 int main(){ 19 memset(dp,-1,sizeof(dp)); 20 cin>>N>>W; 21 for(int i=0;i<N;i++) 22 { 23 cin>>w[i]; 24 cin>>v[i]; 25 } 26 int res=rec(0,W); 27 cout<<res<<endl; 28 return 0; 29 } 30

觀察這個記憶化數組,我們如果把dp[i][j]定義成如下意義:當總重量小於j時,從下標為i的商品開始挑選,得到商品的最大值。於是有下面的遞推公式:

技術分享圖片

這就相當於一個逆向遞推,我們可以利用一個二重循環,利用遞推公式將每一項的值算出來。

技術分享圖片

如同這張表格所示,我們是從下標i=3開始計算(這裏需要把dp二維數組的初始值全化為0,在下面的代碼中,由於定義的是全局數組,對於只有一個輸入樣例的情況下主函數中不必再次初始化,

如果問題有多組輸入,則主函數需要初始化dp數組)。可以自己動手畫一張表格,自己算一算每格的值。這樣很有利於理解下面的代碼。

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstring>
 4 using namespace std;
 5 const int maxN=3405;
 6 const int maxW=405;
 7 int dp[maxN][maxW];
 8 int N,W;
 9 int w[maxW],v[maxN];
10 void  solve(){    
11     for(int i=N-1;i>=0;i--){
12         for(int j=0;j<=W;j++){
13             if(j<w[i]) 
14                 dp[i][j]=dp[i+1][j];
15             else 
16                 dp[i][j]=max(dp[i+1][j],dp[i+1][j-w[i]]+v[i]);
17         }
18     }
19         
20     cout<<dp[0][W]<<endl;
21 }
22 int main(){
23     cin>>N>>W;
24     for(int i=0;i<N;i++)
25     {
26         cin>>w[i];
27         cin>>v[i];
28     }
29     solve();
30     return 0;
31 }
32  

這就是解法三。

當然,有了逆向遞推,我們自然也會想到正向遞推,只不過需要將dp[i][j]的意義重新定義一下。

如果我們將dp[i+1][j]意義定義為:從前i個商品中挑選重量不超過j的的物品時,價值的最大值。(這裏的i是指下標,是從0開始的),就會有下面的遞推公式和表格:技術分享圖片

技術分享圖片

代碼如下:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstring>
 4 using namespace std;
 5 const int maxN=3405;
 6 const int maxW=405;
 7 int dp[maxN][maxW];
 8 int N,W;
 9 int w[maxW],v[maxN];
10 void  solve(){    
11     for(int i=0;i<N;i++){
12         for(int j=0;j<=W;j++){
13             if(j<w[i]) 
14                 dp[i+1][j]=dp[i][j];
15             else 
16                 dp[i+1][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
17         }
18     }
19         
20     cout<<dp[N][W]<<endl;
21 }
22 int main(){
23     cin>>N>>W;
24     for(int i=0;i<N;i++)
25     {
26         cin>>w[i];
27         cin>>v[i];
28     }
29     solve();
30     return 0;
31 }
32  

01背包問題與動態規劃(DP)