首页 > 代码库 > DP——背包问题(一)

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);
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——背包问题(一)