解法一:我們先用最朴素的方法,着眼於每個物體是否進入背包,進行遍歷。
代碼如下:
#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