背包问题总结
背包问题是动态规划( dp )中一类经典题型。
问题的基本情景:你有一个容量为 C 的背包,在商店,有 N 种商品 ,第 i 件商品有 s [ i ] 件,每件该物品价值 c [ i ] ,重量为 w [ i ] ,最多能买价值多少的物品。
这个问题由于条件的不同可以被分为以下略有不同的问题:
- 01背包问题:每种商品只有一件;
- 完全背包问题:每种物品都有无限件;
- 多重背包问题:每种物品的个数各不相同;
那么我们来分析一下这三种背包问题:
一、01背包问题
这是最基本的背包问题,这种背包问题的特点是每种物品只有一件,可以选择放入背包或是不放入背包。最朴素的写法可以使用一个二维数组 f [ i ] [ j ] 来表示前 i 个物品经过选择,剩余容量为 j 时,可以达到的最大价值。
所以易得动态转移方程:
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
所以完整代码如下:
#include<bits/stdc++.h>
using namespace std;
int c,n,w[1005],v[105],f[1005][1005];
int main()
{
scanf("%d%d",&c,&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&w[i],&v[i]);
}
for(int i=1;i<=n;i++){
for(int j=c;j>0;j--){
if(j>=w[i]){
//能装下的话;
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]);
}
else{
//如果装不下;
f[i][j]=f[i-1][j];
}
}
}
printf("%d",f[n][c]);
return 0;
}
上面的代码使用的是二维数组,但是我们知道二维数组里有很多重复的。 这是因为,我们只是在第i-1层对第i层做了更新, 而第i-1层没有发生任何变化,所以我们只需要知道最后一层的情况,而不关心之前的每一层。
因此我们可以得到一个新的动态转移方程:
f[j]=max(f[j],f[j-w[i]]+v[i]);
进而可以得到优化过的代码:
#include<bits/stdc++.h>
using namespace std;
int c,n,w[1005],v[105],f[1005];
int main()
{
scanf("%d%d",&c,&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&w[i],&v[i]);
}
for(int i=1;i<=n;i++){
for(int j=c;j>=w[i];j--){
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
}
printf("%d",f[c]);
return 0;
}
二、完全背包问题
如果不限定每种物品的数量,同一样物品想拿多少拿多少,则问题称为无界或完全背包问题。
按照之前同样的分析方法,我们可以得到动态转移方程:
f[j]=max(f[j],f[j-w[i]]+v[i]);
代码实现:
#include<bits/stdc++.h>
using namespace std;
int n,c,v[10005],w[10005],f[10000005];
int main()
{
scanf("%d%d",&c,&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&w[i],&v[i]);
}
for(int i=1;i<=n;i++){
for(int j=w[i];j<=c;j++){
f[j]=max(f[j],f[j-w[i]]+v[i]);
}
}
printf("%d",f[c]);
return 0;
}
可以利用01背包问题的解决思路,解决完全背包问题。完全背包问题和01背包问题几乎一致,唯一的不同是第二层循环变为从左至右更新。
三、多重背包问题
多重背包问题的思路跟完全背包的思路非常类似,只是物品的个数是有限制的,因此状态转移方程为:
f[j]=max(f[j-k*w[i]]+k*v[i],f[j]);
代码为:
#include<bits/stdc++.h>
using namespace std;
int c,n,w[1005],v[105],s[1005],f[1005];
int main()
{
scanf("%d%d",&c,&n);
for(int i=1;i<=n;i++){
scanf("%d%d%d",&w[i],&v[i],&s[i]);
}
for(int i=1;i<=n;i++){
for(int j=c;j>0;j--){
for(int k=0;k<=s[i];k++){
//多了一层物品取的个数;
if(k*w[i]<=j){
f[j]=max(f[j-k*w[i]]+k*v[i],f[j]);
}
}
}
}
printf("%d",f[c]);
return 0;
}
这三种是最为基础的背包问题,其他的背包问题都是在此基础上建立的,如混合背包就是这三种背包问题的集合,二维背包就是在普通背包问题的基础上增加除了背包以外的限制条件,分组背包就是吧物品额外分组,在每一组里分别取一个或几个物品,只需加一层循环,在那一组里去哪一个物品即可。除此之外还有很多种背包问题,但都万变不离其中,只要理解好前三种背包再稍加变通问题就能迎刃而解。
谢谢大家!