1. 程式人生 > >DP——背包問題(一)

DP——背包問題(一)

理解 什麽 color 組成 循環 space 二進制 scanf !=

以前不是很重視 DP ,遇到 DP 就寫貪心、暴搜……其實這是非常錯誤的,現在開始練習 DP 了才發現,我好菜……

對於DP的整理,先從眾所周知的背包問題開始。

———————— 01背包:n 個物品,重量和價值分別為 w[i]、v[i],背包容量 W,求所有挑選方案中價值總和的最大值。

DP 方程 :dp[j] = max ( dp[j] , dp[ j - w[i] ] + v[i] ) ,其中 dp[j]為使用 j 的容量獲得的最大價值,i 為第 i 件物品。

代碼:

技術分享
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<algorithm>
 5 #include<cmath>
 6 #include<string>
 7 using namespace std;
 8 int n,W,w[2000],v[2000],dp[2000];
 9 int main(){
10     scanf("%d %d",&n,&W);
11 for(int i=1;i<=n;i++) scanf("%d %d",&w[i],&v[i]); 12 for(int i=1;i<=n;i++) 13 for(int j=W;j>=w[i];j--){ 14 dp[j]=max(dp[j],dp[j-w[i]]+v[i]); 15 } 16 printf("%d",dp[W]); 17 return 0; 18 }
01背包

———————— 完全背包:n 種物品,數目不限,其他的和 01背包 一 樣。

DP 方程 : dp[j] = max ( dp[j] , dp[ j - w[i] + v[i] ) ,是不是感覺和 01背包 一樣?其實,就是 一樣……-- ^ --|||||,但代碼有細微差別……

代碼:

技術分享
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<algorithm>
 5 #include<cmath>
 6 #include<string>
 7 using namespace std;
 8 int n,W,w[2000],v[2000],dp[2000];
 9 int main(){
10     scanf("%d %d",&n,&W);
11     for(int i=1;i<=n;i++) scanf("%d %d",&w[i],&v[i]);
12     for(int i=1;i<=n;i++)
13     for(int j=w[i];j<=W;j++){
14         dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
15     }
16     printf("%d",dp[W]);
17     return 0;
18 }
完全背包


那麽,問題來了,為什麽 01背包 是倒著 循環,而 完全背包 是正著循環……動筆模擬一下,你會發現且理解 — 正著循環會對一件物品重復選取╮(╯_╰)╭,而倒著循環就不會……

———————— 多重背包:n 種物品,給定數目 m[i],其他和 01 背包一樣。

DP 方程:dp[j] =max ( dp[j],dp[ j - w[i] ] + v[i] ),&%&……¥怎麽又一樣,其實 多重背包 就是特殊處理化的 01背包,這個特殊處理就是 二進制拆分。

二進制拆分:眾所周知什麽是二進制,二進制拆分就是把 m[i] 個物品拆成 1個物品、2個物品組成的新物品、4個物品組成的新物品、8個物品組成的新物品……一 直到不能拆為止,因為二進制可以 表示 出任何實數的嘛。

代碼:

技術分享
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<algorithm>
 5 #include<cmath>
 6 #include<string>
 7 using namespace std;
 8 int n,W,w[2000],v[2000],dp[2000],m[2000];
 9 int main(){
10     scanf("%d %d",&n,&W);
11     int x=n;
12     int y;
13     for(int i=1;i<=n;i++) scanf("%d %d %d",&w[i],&v[i],&m[i]);
14     for(int i=1;i<=x;i++){
15         y=1;
16         while(m[i]>y&&m[i]>1){
17             n++;
18             w[n]=y*w[i];
19             v[n]=y*v[i];
20             m[i]-=y;
21             y=y*2;
22         }
23         w[i]=m[i]*w[i];
24         v[i]=m[i]*v[i];
25     }
26     for(int i=1;i<=n;i++)
27     for(int j=W;j>=w[i];j--){
28         dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
29     }
30     printf("%d",dp[W]);
31     return 0;
32 }
多重背包

———————— 三種混合背包(01背包、完全背包、多重背包):這種問題就是給定 m[i],若 m[i] 為 -1,則為數目無限,否則數目有限,其他的和 01背包 一樣。雖然寫著是三種 混合背包,但我認為實際上是兩種背包,多重背包 和 完全背包。方法就是能拆分的就拆分,在循環的時候,判斷一下,如果是 完全背包 就正著循環,不然就倒 著循環…………蠻easy的QAQ~

代碼:

技術分享
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<algorithm>
 5 #include<cmath>
 6 #include<string>
 7 using namespace std;
 8 int n,W,w[2000],v[2000],dp[2000],m[2000];
 9 int main(){
10     scanf("%d %d",&n,&W);
11     int x=n;
12     int y;
13     for(int i=1;i<=n;i++) scanf("%d %d %d",&w[i],&v[i],&m[i]);
14     for(int i=1;i<=x;i++) if(m[i]!=-1) { 
15         y=1;
16         while(m[i]>y&&m[i]>1){
17             n++;
18             w[n]=y*w[i];
19             v[n]=y*v[i];
20             m[i]-=y;
21             y=y*2;
22         }
23         w[i]=m[i]*w[i];
24         v[i]=m[i]*v[i];
25     }
26     for(int i=1;i<=n;i++) 
27         if(m[i]!=-1)
28         for(int j=W;j>=w[i];j--){
29             dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
30     }
31     else 
32         for(int j=w[i];j<=W;j++)
33              dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
34     printf("%d",dp[W]);
35     return 0;
36 }
混合背包

DP——背包問題(一)