1 题目
(此题来源于郭炜《算法基础与在线实践》,结合教材和自己体会所写)
1.1 问题表述
有n种物品和容量为m的背包,每个物品可以选择放或者不放在背包里面(物品最多可以被选择一次),每个物品的体积为wi和价值为di,问背包价值最大为多少。
通俗来讲,假设小黑去超市购物,购物车大小一定,每个商品的价值和大小都不一样,问怎样装购物车装满的情况下物品总价值最大。01背包问题就是每种物品至多拿一个,而完全背包问题是每种物品可以拿多个。
1.2 输入数据
第一行为整数N(物品种类)和M(背包容量)。从第二行到最后一行每行有两个整数,分别代表物品的体积wi和价值di。
1.3 输入
4 6
1 4
2 6
3 12
2 7
1.4 输出
23
2 问题分析
此题共有n中物品,每种物品可以选择放或者不放在背包里面,穷举所有可能,一共有2的n次方种方法,显然时间复杂度过高,n只要稍微大一些,就会超时。
所以要选用其他方法。对于该问题,对于所有物品,至少要遍历一遍,来决定其放或者不放,所以物品的种类就是外层循环。当遍历到第i种物品时,有两种状态,放或者不放,用F[i]表示放到第i种物品的价值,F[i] = max(放第i种物品,不放第i种物品),当放第i种物品时,也就意味着背包的剩余容积会变小,相应的总价值会增加。
上面所述,把物品的种类当作第一层循环,则可以把背包的容积当作第二层循环。对于第二层循环,可以想象当背包容量为0时、为1时、为k时、直至为m时,而以此为角标对数组进行取值,F[k]就是背包容量为k时的价值。也就是说用物品种类作为第一层循环,和背包容量作为第二层循环遍历了所有可能,然后进行实时更新覆盖掉次优解。
3 代码编写
#include <iostream>
#include <algorithm>
using namespace std;
struct Good { //物品结构体,包含物品体积和价值两个属性
int w; //体积
int d; //价值
};
void test() {
int N, M;
cin >> N >> M; //N代表物品的种类,M代表背包的容积
Good* Goods = new Good[N + 1]; //物品数组
int* F = new int[M + 1]; //背包容积数组
for (int i = 1; i <= N; i++) { //为了方便计数,角标以1开始
cin >> Goods[i].w >> Goods[i].d; //初始化物品数组
}
for (int i = 0; i <= M; i++) { //初始化背包容积的价值数组
F[i] = 0;
}
//开始写两层循环,外层循环用物品种类,内层循环用背包容积,遍历物品种类所有可能和背包容积的所有可能
for (int i = 1; i <= N; i++) { //从第1个物品开始遍历
for (int j = M; j >= 0; j--) { //背包容积遍历
if (Goods[i].w <= j) { //如果第i种物品的体积小于容积为j时背包的容量,则表示该物品可以被放进去
F[j] = max(F[j], F[j - Goods[i].w] + Goods[i].d);
//F[j]表示不放第i种物品,容积不变,价值不变
//F[j-Goods[i].w]+Goods[i].d表示放第i种物品,容积变小,价值变大
}
}
}
cout << F[M] << endl;
}
int main() {
test();
return 0;
}
、