动态规划-多重背包-详解

多重背包的本质就是01背包

有一个小偷,他带着容量为v的背包去偷东西,摆在他面前的是n样东西(每一个东西可能不止1件),它们都有自己的体积和价值,求解小偷能拿取的最大价值。
输入:
第一行有2个整数,分别是v,n
接下来的n行分别是每一样东西的体积和价值、
输入样例:
100 3
71 100 10
69 1 9
1 2 17
输出样例:
134

为什么说多重背包的本质就是01背包呢,比如说下面这个物品列表,我们可以直接转换成01背包的输入。
转化演示表
如此我们可以发现,其实件数如果大于1,那么把每一件看作一种新的物品,然后用之前我们学习的算法内容,进行计算就行了。无非是在之前的双重循环内再添加一层循环不就行了

但是,我们得考虑复杂度的问题。倘若物品的数量是1000或者10000的话,随着数量的增加,之前算法的时间会激增。

那么该如何简化这个过程呢?

那么我们就得先学会一个新的算法,看题!

tom老师在一个展会上游荡,他身负一千硬币,可以进行全场最佳的投票,请你将这1000枚分成几堆,方便tom老师进行任何数量的打赏。

看题,我们知道理解一个问题,如果tom老师想要进行打赏250枚硬币的操作,那么tom老师需要进行数硬币的操作250次,每次数一枚。这个过程就很浪费时间,所以才有分成几堆硬币,这几堆硬币的数量可以组合成1~1000之内的任意数字,那么数硬币的循环就被极大的降低了。

可以这样分

演示表
其实,我们通过二进制形式可以直接看出来第一堆到第九堆的硬币数量可以组合成1~256内的所有数字,加上剩余的489就可以组成1到1000之内的所有数字。

同样的道理,如果有一个物品有1000件,我们可以将该物品拆解为10种物品,其中一件为一种,两件组合起来为一种,四件组合起来为一种…

相较之前的1000件直接拆解成1001件,大大减少了运算时间。那么为什么这样拆解可以解决问题呢,因为我们知道其实本质上,我们的解法也会遍历所有的组合方式,我们将其拆解为这10中,这10中任意组合也能组成1000内的所有数量的可能,所以相对来说,我们大大减少了重复运算的步骤。

好,理论可行,代码呈上!

#include <iostream>
#include <cstring>
using namespace std;
int main () {
//  有一个小偷,他带着容量为v的背包去偷东西,摆在他面前的是n样东西(每一个东西可能不止一件),它们都有自己的体积和价值,求解小偷能拿取的最大价值。
//	输入:
//	第一行有2个整数,分别是v,n
//	接下来的n行分别是每一样东西的体积、价值和数量
//	输入样例:
//	100 3
//	71 100 10
//	69 1 9
//	1 2 17
//	输出样例:
//	134
	int v, n;
	cin >> v >> n;
	// 定义物品价值、所占空间和数量的数组还有dp数组,数组长度取决于n即物品的件数
	int val[100], vol[100], nums[100], dp[100] = {0};
	for (int i = 1; i <= n; i++) {
		cin >> vol[i] >> val[i] >> nums[i];
	}
	
	// 接下来进行数量的拆解,由于原先的某一种物品可能会被拆成几堆,
	// 每一堆作为一种新物品,所以我们需要新建一个新价值和体积数组
	int tmp_val[100], tmp_vol[100];
	// 定义一个变量用以记录新物品在上面数组中的位置
	int index = 1;
	// 外层循环用于遍历最初每一种物品,内循环则是对每一种物品进行分堆
	for (int i = 1; i <= n; i++) {
		// 该循环的变量j即是每一堆物品的数量
		for (int j = 1; j <= nums[i]; j *= 2) {
			// 每一堆物品的价值和体积则为总和,计算后放入新数组作为一种
			// 新物品
			tmp_val[index] = val[i] * j;
			tmp_vol[index] = vol[i] * j;
			nums[i] -= j;
			index++;
		}
		// 对数量进行判断,如果数量不为零,剩下的所有物品作为一堆
		if (nums[i] != 0) {
			tmp_val[index] = val[i] * nums[i];
			tmp_vol[index] = vol[i] * nums[i];
			index++;
		}
	}
	
	// 完成分堆后,dp数组赋值操作与之前一模一样,注意由于物品
	// 数量会有改变,那么外层循环的限制条件将需要改变
	// 开始写循环进行数组的赋值
	for (int i = 1; i < index; i++) {
		for (int j = v; j > 0; j--) {
			// i代表物品编号,j代表背包空间
			// 做第一层判断,背包空间是否能够装下第i件物品
			if (j >= tmp_vol[i]) {
				// 空间够的情况下,取价值高的那情况
				dp[j] = max(dp[j], dp[j - tmp_vol[i]] + tmp_val[i]);
			} else {
				// 空间不够,直接选择不拿第i件物品
				dp[j] = dp[j];
			}
		}
	}
	// 输出空间为v,取所有n件物品的值,即题目要求的答案
	cout << dp[v];
}

其实多重背包的难点在于对每种物品熟练的拆分,我们需要理解为什么要按二进制进行拆分,为什么这样拆可以组成所有可能的数据,为什么这样拆了以后依然可以得到正确答案。
我的解析更多的是启发你思考的引子,如有任何问题欢迎私信讨论!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值