DP——背包問題(一)
以前不是很重視 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);01背包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 }
———————— 完全背包: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——背包問題(一)