我们前面已经讲了01背包和完全背包问题,其实多重背包就是由01背包引申而来的,所以没看过01背包问题的建议先去看看。
01背包二维数组
01背包一维数组
另外解决多重背包问题,学会完全背包也是个关键。
完全背包问题之二维数组解法
完全背包问题之一维数组解法
由于多重背包的解法有很多,这次就先讲用“朴素算法”求解。
题目如下:
【题目介绍】
【题目描述】
为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。
【输入】
第一行二个数 n n n( n ≤ 500 n\le500 n≤500), m m m( m ≤ 6000 m\le6000 m≤6000),其中 n n n代表希望购买的奖品的种数, m m m表示拨款金额。
接下来n行,每行3个数, v 、 w 、 s v、w、s v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到s件均可),其中 v ≤ 100 , w ≤ 1000 , s ≤ 10 v\le100,w\le1000,s\le10 v≤100,w≤1000,s≤10。
【输出】
一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。
【输入样例】
5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1
【输出样例】
1040
【数据储存】
和01背包,完全背包问题基本一样,物品数量和背包容量分别用两个变量储存,每个物品的重量、价值和数量都用结构体储存即可。
【算法分析】
和01背包、完全背包一样,都用动态规划算法解决。
【dp数组含义】
其实01背包、完全背包和多重背包问题中dp数组的含义都是一样的,二维dp数组都是指物品 i 放进背包容量为 j 的背包里的最大价值。
【填表】
这次为什么要先把这个放到【递推公式】前面呢?因为多重背包问题要想明白递推公式,要先知道它是如何填表的。
多重背包的循环顺序其实和01背包、完全背包差不多,第一层循环循环物品,第二层循环循环背包,但不同的是,要有第三层循环,用来循环物品数量。
故循环顺序为:
for(int i=2;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=0;k<=s[i].s&&k*s[i].w<=j;k++){
递推公式
}
}
}
【递推公式】
虽然含义一样,但是递推公式还是有一些不同的。
01背包的是:
当 j < s[i].w 时,dp[i][j]=dp[i-1][j];
否则,dp[i][j]=max(dp[i-1][j],dp[i-1][j-s[i].w]+s[i].v);
完全背包的是:
当 j < s[i].w 时,dp[i][j]=dp[i-1][j];
否则,dp[i][j]=max(dp[i-1][j],dp[i][j-s[i].w]+s[i].v);
但是,由于还要加上每种情况下对物品数量的循环,所以应改为:
当 j < s[i].w 时,dp[i][j]=dp[i-1][j];
否则,dp[i][j]=max(dp[i-1][j],dp[i-1][j-ks[i].w]+ks[i].v);
【dp数组初始化】
第0列指的是将物品 i 装进背包容量为 0 的背包里,很明显装不了,所以这一列都为0.
第一行其实和完全背包的初始化差不多,但是比完全背包要多一个条件,那就是数量不能超过本物品所有的数量,若超过,则dp[1][i]=dp[1][i-1];否则,dp[1][i]=i/s[i].w*s[i].v;
最后附上AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 15000;
int dp[maxn][maxn];
struct ss{//利用结构体储存数据
int v,w,s;
}ss1[maxn];//避免重名
int main(){
int n,m;
cin>>n>>m;//表示物品数量和背包容量
for(int i=1;i<=n;i++)cin>>ss1[i].w>>ss1[i].v>>ss1[i].s;
for(int i=1;i<=n;i++){//初始化第0列
dp[i][0]=0;
}
for(int i=0;i<=m;i++){//初始化第一行
if(i>ss1[1].w ){
if(i/ss1[1].w<=ss1[1].s){
dp[1][i]=i/ss1[1].w*ss1[1].v;
}
else{
dp[1][i]=dp[1][i-1];
}
}
else{
dp[1][i]=0;
}
}
for(int i=2;i<=n;i++)//填表
for(int j=1;j<=m;j++)
for(int k=0;k<=ss1[i].s&&k*ss1[i].w<=j;k++)
dp[i][j]=max(dp[i][j],dp[i-1][j-k*ss1[i].w]+k*ss1[i].v);
cout <<dp[n][m]<< endl;
return 0;
}
从【填表】这里我们可以发现,“朴素算法”需要三次for循环,若数据范围到达1000时,就会超时。
所以需要优化。
下期预告:用二进制优化多重背包问题