2021-10-12

本文探讨了动态规划在解决背包问题中的应用,包括01背包问题和多重部分和问题。通过建立二维或一维数组,优化算法以避免枚举所有可能情况导致的时间复杂度过高。还介绍了如何处理有限数量的物品,以及如何使用容斥原理解决有数量限制的硬币找零问题。最后,给出了具体的代码实现。
摘要由CSDN通过智能技术生成

动态规划:

先看看01背包:

有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

最直观的解法是:一个一个算:
第一个到第i个都放进背包,看看价值多大,有没有超重?
第一个到第i-1个都放进背包,
第一个,第二个,…第i个 但是第i-1个不放进背包

依次类推
背包里面有i个物品的情况有1种;
有n-1个物品的情况有i种;
有n-2个的时候有C(n,2)种,在i个里面取两个的排列组合

显然,当n比较大的时候,这个工程量会比较大,并且每一种情况都是经过枚举得来的,很容易超时。

引入动态规划
第i种无非就是两种状态,放进背包和不放进背包
建立一个二维数组
dp[i][j]= 前i个物品组成重量为j时的最大价值。
接下来进行2重循环,
在这里插入图片描述
分析一下
每次第i+1个有两种选择,进背包和不进背包,如果背包的重量大于了此时的j时(假设W变为了j),那肯定不进背包了。进了背包之后,看看不进和进两种情况那个比较大,取大的哪一种,保证了的dp[i][j]每次都是最优解。第i+1个肯定由i-1个推出的,第i个肯定是i-1推出的。
上述还可以优化一下成为一维数组
在这里插入图片描述
dp[j]表示背包容重量为j时能装的最大价值。
为什么能变为一维数组?
也是一样的,从第一个物品开始,取和不取的两种情况,取最大值。
对于第i个物品,假设让背包最大价值时它取了3次,那么它取了3次是从取了2次推来的,取了2次是从取了1次上推导出来的。得到一个最大时替换掉自己。

问题变一下,变为多重部分和:
有n种不同大小的数字a(i),每种都有无数个,判断是否可以从这些数字之中选出若干个使他们的和恰好为K,如果可以,那么有多少种?
先初始化dp[0]=1;![在这里插入图片描述](https://img-blog.csdnimg.cn/1b7497df77b34bf68a3226d6a338acc1.png
这段代码非常巧妙,也是一位数组
dp[j]代表恰好为j时有多少种。
先从i=1开始分析,(假设存在)dp[i*2]是从dp[i]加上i得到的,能得到,那么它就不会为0了。这样不好理解,举个例子:
有两个数字 2 和4
现在我们来算下dp[8]
i=1时,明显
dp[2]=dp[4]=dp[6]=dp[8]=1; 都只有一种组成方法
i=2时,
dp[4]=dp[4]+dp[0];
dp[6]=dp[6]+dp[2];
dp[8]=dp[8]+dp[4];
4 取1 次 的结果是由前面的结果加上取这一次的结果得到的。
前面dp[4]经过2的取放变为了1种。这个dp[4]是经过了前面i-1个数的处理的。
就是前面i-1种数组成4有多少种组法再加上取i时的组法。取i时也是递加的,取一个i的时候,取2个i的时候,都用到了之前的结果。

看一下一道题目

在这里插入图片描述
在这里插入图片描述
这里条件改变了一下,对于每种数字的数量都做了限制,不再是无限次数了。用多重部分和问题解决(有数字个数限制的)时间复杂度可以优化到O(NK)(和*数字个数),这里不进行分析了。毫无疑问会超时.
这里,我们假设一下,假如这4种硬币都有无限个就好了,有无限个的时候不管你去多少次结果都是一样的,可以去掉了n。
有无限个的次数=超过的次数+在规定范围内的次数。
那么我们将无限次数减去超过的次数不就可以得到答案了吗?只有计算每次的超出部分再将它减去就可以了。
那么超过的次数怎么计算。
dp[s]就是假设用了无限制次数得出的答案。
如果第i个假设这里是第1个超过了限制的次数
那么第1个取了肯定是超过了d[1]+1个。
(d[1]+1)*c[1]就为超出的无效范围.
dp[s-(d[1]+1)*c[1]]就是第1个超出的次数。
为什么?
先强制给d[1]+1个第1种硬币,这表示了你以后不管再给多少个第1种硬币,就算是给0个,那么这也表示超出了。
你给了d[1]+1个第1种硬币之后,后面要给的钱就是s-(d[1]+1)*c[1],那么有多少种方法可以组成s-(d[1]+1)*c[1]呢?就是dp[s-(d[1]+1)*c[1]]了。
明白了这里之后问题也就迎刃而解了,
不过还要了解一下容斥原理。
简单地说就是
要计算几个集合并集的大小,我们要先将所有单个集合的大小计算出来,然后减去所有两个集合相交的部分,再加回所有三个集合相交的部分,再减去所有四个集合相交的部分,依此类推,一直计算到所有集合相交的部分。

最后双手附上代码

#include<iostream>
using namespace std;
long long dp[100006] = {0};
int c[5], d[5];
long long max(long long x, long long y) {
	return x > y ? x : y;
}
int main() {
	long long n, q, i, j;
	for (i = 1; i <= 4; i++)
		cin >> c[i];
	
	dp[0] = 1;
	for (i = 1; i <= 4; i++)					//完全背包,没有限制时
			for (j = c[i]; j <= 100005; j++)        //大于价值s最大
				dp[j] += dp[j - c[i]];				//假设都不取时有几种取的情况

cin >> q;
	while(q--)
	{
		long long s;
		for (i = 1; i <= 4; i++)		//数量
			cin >> d[i];
		cin >> s;						//要买的价值
		long long f1 = (d[1] + 1) * c[1];
		long long f2 = (d[2] + 1) * c[2];
		long long f3 = (d[3] + 1) * c[3];
		long long f4 = (d[4] + 1) * c[4];

		// 1种超额
		long long ans1=0;
		if (s - f1 >= 0)ans1 += dp[s - f1];
		if (s - f2 >= 0)ans1 += dp[s - f2];
		if (s - f3 >= 0)ans1 += dp[s - f3];
		if (s - f4 >= 0)ans1 += dp[s - f4];

		//2种超额  6种情况
		long long ans2 = 0;
		if (s - f1 - f2 >= 0)ans2 += dp[s - f1 - f2];
		if (s - f1 - f3 >= 0)ans2 += dp[s - f1 - f3];
		if (s - f1 - f4 >= 0)ans2 += dp[s - f1 - f4];
		if (s - f3 - f2 >= 0)ans2 += dp[s - f3 - f2];
		if (s - f4 - f2 >= 0)ans2 += dp[s - f4 - f2];
		if (s - f3 - f4 >= 0)ans2 += dp[s - f3 - f4];

		//3种超额
		long long ans3 = 0;
		if (s - f1 - f2 - f3 >= 0)ans3 += dp[s - f1 - f2 - f3];
		if (s - f1 - f2 - f4 >= 0)ans3 += dp[s - f1 - f2 - f4];
		if (s - f3 - f2 - f4 >= 0)ans3 += dp[s - f3 - f2 - f4];
		if (s - f1 - f3 - f4 >= 0)ans3 += dp[s - f1 - f3 - f4];

		//4种超额

		long long ans4 = 0;
		if (s - f1 - f2 - f3 - f4 >= 0)ans4 += dp[s - f1 - f2 - f3 - f4];

		long long sum = 0;
		sum = dp[s] - ans1 + ans2 - ans3 + ans4;
		cout << sum << endl;

	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值