/*
Name: 多重背包问题
Copyright:
Author:
Date: 28-07-17 10:24
Description:
多重背包问题:在n种物品中选取若干件(第i种物品最多选N[i]次)放在容量为c的背包里,分别用P[i]和W[i]存储第i种物品的价值和重量。
求解怎么装物品可使背包里物品总价值最大。
输入
第一行2个数n和c,表示共有n种物品,背包总容量为c
接下来n行,每行3个数,分别表示第i种物品的重量,价值和最大数量
输出
一个整数,表示背包里物品最大总价值
样例输入
5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1
样例输出
1040
算法思想:
算法1:二维数组+朴素的穷举思想
利用函数式B[i][j] = max(B[i-1][j-K*W[i]]+K*P[i]), 0<=K<=N[i]且0<=K*P[i]<=j,对k进行穷举,找出最大值。
该算法与完全背包问题的朴素算法相似,只是多了一个0<=K<=N[i]的条件。
算法2:一维数组+朴素的穷举思想
该算法仍然使用穷举思想,但是用一维数组F[j]代替了二维数组B[i][j],也就是未记录行坐标,只记录了列坐标,
与0-1背包问题类似,注意到第i行第j列的元素,由第i-1行的元素决定,且列坐标j大的元素由j小的元素决定,
故在同一行中,必须先求出列坐标较大的元素,再求列坐标小的元素,即在内层循环中,应该让循环变量j的值从大到小递减。
算法3:2个一维数组+对第i种物品进行N[i]次选择
算法1和算法2都采用了朴素的穷举思想,在给定i个物品的情况下,对k进行穷举,找出最大值。
有一个更为巧妙的思想可以减少部分条件判断语句,那就是把多重背包问题当做多个0-1背包问题来处理,
即对第i个物品执行N[i]次选择。这样循环语句for (int k=0; k<N[i]; k++)只需要表示执行N[i]次选择即可,思路更清晰了。
与单纯0-1背包不同的是,对物品i进行第k次选择时,背包中可能已经装载了第i个物品了,
所以不能用B[i][j]来表示给定i个物品容量为j时的最大价值,但是可以用两个一维数组来表示,
其中cur[j]表示给定i个物品的情况下,背包容量为j时,对物品进行第k次选择时所能获得的最优解,
pre[j]表示给定i个物品的情况下,背包容量为j时,对物品进行第k-1次选择时所能获得的最优解,
值得注意的是pre[j]并不表示给定i-1个物品的情形,但是当j<W[i]时,pre[j]的值与给定i-1个物品的情形一致,
所以即使N[i]==1(0-1背包),算法也不会出错。
算法4:1个一维数组+对第i种物品进行N[i]次选择
与0-1背包或完全背包算法一样,多重背包问题也可以用1个一维数组来代替2个一维数组,
注意到cur[]和pre[]分别代表当前行和上一行,且cur[j]只与pre[j]和pre[j-W[i]]有关,
我们可以逆序扫描j,这样只需要一个数组F[]就能代替cur[]和pre[]了。
此时代码的结构与0-1背包基本相同,只是在中间插入了一个循环语句for (int k=0; k<N[i]; k++),
表示对第i种物品进行N[i]次0-1选择,实在是妙不可言!
由此可见,0-1背包问题是多重背包问题的一个特例(N[i]=1),因此他们的代码结构必然是相同的,
如果多重背包问题的第2层循环for (int k=0; k<N[i]; k++)只执行一次的话,就变成了一个纯粹的0-1背包问题。
算法5:二进制优化+转化为0-1背包问题
进行二进制优化,把第i种物品拆成重量为W[i]*2^k价值P[i]*2^k的物品,其中满足1+2+...+2^k = N[i]。
例如,对N[6]={0,4,9,7,6,1}分别进行拆分,有4=1+2+1, 9=1+2+4+2, 7=1+2+4, 6=1+2+3, 1=1.
此算法需要在输入数据时就对物品进行拆分,相当于增加了新的物品,这样就可以转换为0-1背包问题。
*/
#include<iostream>
#include<cmath>
using namespace std;
const int MAXC = 6000; //背包最大容量
const int MAXN = 2000; //物品的个数
int W[MAXN+1];//物品的重量
int P[MAXN+1];//物品的价值
int N[MAXN+1];//物品的最大数量
int F1[MAXC+1]; //记录装入容量为c的背包的最大价值
int B1[MAXN+1][MAXC+1]; //备忘录,记录给定n个物品装入容量为c的背包的最大价值
int pre[MAXC+1]; //pre[j]相当于B1[i-1][j]
int cur[MAXC+1]; //cur[j]相当于B1[i][j]
int F2[MAXC+1]; //记录装入容量为c的背包的最大价值
int B2[MAXN+1][MAXC+1]; //备忘录,记录给定n个物品装入容量为c的背包的最大价值
int F3[MAXC+1]; //记录装入容量为c的背包的最大价值
int MultiPack_1(int n, int c);//多重背包问题:二维数组+朴素的穷举思想
int MultiPack_2(int n, int c);//多重背包问题:一维数组+朴素的穷举思想
int MultiPack_3(int n, int c);//多重背包问题:2个一维数组记录最优解
int MultiPack_4(int n, int c);//多重背包问题:一维数组+对第i种物品进行N[i]次0-1选择
int ZeroOnePack(int n, int c);//0-1背包问题:一维数组记录最优解
int main()
{
int n, c;
cin >> n >> c;
for (int i=1; i<=n; i++)//不计下标为0的元素
{
cin >> W[i] >> P[i] >> N[i];
}
cout << MultiPack_1(n, c) << endl;
cout << MultiPack_2(n, c) << endl;
cout << MultiPack_3(n, c) << endl;
cout << MultiPack_4(n, c) << endl;
//
// //进行二进制优化,把第i种物品拆成重量为W[i]*2^k价值P[i]*2^k的物品,其中满足1+2+...+2^k = N[i]。
// //需要在输入数据时就对物品进行拆分,相当于增加了新的物品,这样就可以转换为0-1背包问题
// int len = 0;//累计拆分后物品的总数量,因为第i种物品可以选多个,相当于增加了物品的种类,每种物品只有0-1选择
// for (int i=0; i<n; i++)
// {
// int w, p, s, t=1;
// cin >> w >> p >> s;
// //把s以2的指数幂分堆:1,2,4,...,2^(k-1), s-2^k+1
// while (s > t)//注意不能写成while (s >= t),否则当s=2^k-1时,会拆分出重量和价值都为0的新物品,虽然不影响最终结果,但效率变低
// {
// W[++len] = w * t;
// P[len] = p * t;
// s -= t; //注意s在变小
// t *= 2; //t是2的指数幂
// }
// W[++len] = w * s;
// P[len] = p * s;
// }
// //获得新的物品的种类数据后,直接当做0-1背包问题处理
// cout << ZeroOnePack(len, c) << endl;
return 0;
}
int MultiPack_1(int n, int c)//多重背包问题:二维数组+朴素的穷举思想
{
int bestP;
for (int i=1; i<=n; i++)
{
for (int j=1; j<=c; j++) //j递增或递减均可
{
//朴素的穷举思想,计算第i个物品装k个时,获得多少价值,存储最大价值到B[i][j]
bestP = 0;
for (int k=0; k<=N[i] && k*W[i]<=j; k++)
{
if (bestP < B1[i-1][j-k*W[i]] + k*P[i])
bestP = B1[i-1][j-k*W[i]] + k*P[i];
}
B1[i][j] = bestP;
}
}
return B1[n][c];
}
int MultiPack_2(int n, int c)//多重背包问题:一维数组+朴素的穷举思想
{
int bestP;
for (int i=1; i<=n; i++)
{
for (int j=c; j>=W[i]; j--) //类似于0-1背包问题,j只能递减
{
//朴素的穷举思想,计算第i个物品装k个时,获得多少价值,存储最大价值到F[j]
bestP = 0;
for (int k=0; k<=N[i] && k*W[i]<=j; k++)
{
if (bestP < F1[j-k*W[i]] + k*P[i])
bestP = F1[j-k*W[i]] + k*P[i];
}
F1[j] = bestP;
}
}
return F1[c];
}
//
//int MultiPack_3(int n, int c)//因为B2[i][j]不能及时迭代,故算法错误
//{
// int maxNum = 0; //记录对第i种物品进行0-1选择的最多次数
//
// for (int i=1; i<=n; i++)
// {
// maxNum = c / W[i]; //即使是完全背包,也最多选择maxNum次
// if (N[i] > 0 && maxNum > N[i]) //非完全背包
// {
// maxNum = N[i];
// }
// for (int k=maxNum; k>0; k--)//对第i种物品进行maxNum次选择
// {
// for (int j=1; j<=c; j++)
// {
// if (j < W[i]) //容量不够,则和给定i-1个物品装入容量为j的背包的结果一致
// {
// B2[i][j] = B2[i-1][j];
// }
// else //B2[i][j-W[i]]表示给定i个物品装入容量为j-W[i]的背包,质量为W[i]的物品可能已经装了多个
// {
// B2[i][j] = max(B2[i-1][j], B2[i][j-W[i]] + P[i]);
// }
// }
// }
// }
//
// return B2[n][c];
//}
int MultiPack_3(int n, int c)//多重背包问题:2个一维数组记录最优解
{
//cur[j]表示给定i个物品的情况下,背包容量为j时,对物品进行第k次选择时所能获得的最优解
//pre[j]表示给定i个物品的情况下,背包容量为j时,对物品进行第k-1次选择时所能获得的最优解
for (int i=1; i<=n; i++)
{
for (int k=0; k<N[i]; k++)//对第i种物品进行k次选择
{
for (int j=1; j<=c; j++)
{
if (j < W[i] || pre[j] > pre[j-W[i]] + P[i])
cur[j] = pre[j];
else
cur[j] = pre[j-W[i]] + P[i];
}
for (int j=1; j<=c; j++)//更新后的pre[j]有可能是选择了第i种物品
{
pre[j] = cur[j];
}
}
}
return pre[c];
}
int MultiPack_4(int n, int c)//多重背包问题:一维数组+对第i种物品进行N[i]次0-1选择
{
for (int i=1; i<=n; i++)
{
for (int k=0; k<N[i]; k++)//对第i种物品进行N[i]次0-1选择
{//须先求出列坐标j较大的元素,故让循环变量j的值从大到小递减
for (int j=c; j>=W[i]; j--)
{//当(j < W[i] || F2[j] > F2[j-W[i]] + P[i])时,F2[j]的值不变
if (F2[j] < F2[j-W[i]] + P[i])
F2[j] = F2[j-W[i]] + P[i];
}
}
}
return F2[c];
}
int ZeroOnePack(int n, int c)//0-1背包问题:一维数组记录最优解
{
for (int i=1; i<=n; i++)
{//须先求出列坐标j较大的元素,故让循环变量j的值从大到小递减
for (int j=c; j>=W[i]; j--)
{//当(j < W[i] || F3[j] > F3[j-W[i]] + P[i])时,F3[j]的值不变
if (F3[j] < F3[j-W[i]] + P[i])
F3[j] = F3[j-W[i]] + P[i];
}
}
return F3[c];
}
多重背包问题
最新推荐文章于 2022-07-16 20:01:15 发布