以前不是很重视 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 }
混合背包