[洛谷 P1450 HAOI2008] 硬币购物 (背包+容斥)

P1450 [HAOI2008]硬币购物


题目大意:

首先给出四种不同面值的硬币 第i种硬币的面值为c_i, n次询问,每次询问输入4种面值硬币的数量d_i,和需要购买的物品价值s,求有多少种付款方式 


题目分析:

本题如果单纯使用多重背包来做 是O(n * logd * s)的复杂度会超时

如果本题改成硬币没有数量限制,我们就可以直接利用完全背包跑出来复杂度为O(4*s) ,但是现在有限制这样跑出来会有不合法情况,所有就有这样一个思路就是 用总方案呢数量-不合法方案数量 就是合法的方案数量,现在假如有一种方案的我们最少用d_1+1个硬币 那么该方案数其实就是f[s-(d_1+1)*c_1] 这就是不合法的方案数因为第一个硬币一定超过了限制,现在再复杂一些比如有3种硬币都超过限制 如果我们把三种超出限制的方案逐一减去,这是就可能会减重(即同时两个硬币有限制的情况减了两次)所以这里就需要使用容斥原理了

容斥原理:A∪B∪C = A+B+C  –  A∩B  – B∩C – C∩A  + A∩B∩C(奇加偶减)

因为只有四种硬币直接把所有超过限制的方案枚举一下就可以了(只有16种情况,例如:第1个硬币超限,第1,2个硬币超限,第1,3,4个硬币超限)这里可以用4位的二进制来表示状态

这样复杂度就可以将为O(4s+2^6n)


  下边模型截取自:1E6 的 [洛谷 P1450 HAOI2008] 硬币购物 (容斥应用好题)


AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
ll c[5], d[5];
ll f[N];//f[i]表示容量是i的总方案数(包括不合法的)
int main(void)
{
	int n,s;
	for (int i = 1; i <= 4; i++) cin >> c[i];

	//完全背包 直接预处理除所有方案数
	f[0] = 1;
	for (int i = 1; i <= 4; i++)
		for (int j = c[i]; j < N; j++)
			f[j] += f[j - c[i]];

	cin >> n;
	while (n--)
	{
		for (int i = 1; i <= 4; i++) cin >> d[i];
		cin >> s;
		//下边枚举15种不合法情况 0000~1111
		//0001表示第一个硬币不合法 1010表示第四和第二个硬币不合法
		ll res = 0;//不合法的数量
		for (int i = 1; i <= 15; i++)
		{
			ll sum = s, cnt = 0;//cnt表示不合法的硬币个数
			for (int j = 0; j < 4; j++)
			{
				//判断第i种情况的第j+1个硬币是否合法
				if (i & (1 << j))
				{
					cnt++;
					sum -= (d[j + 1] + 1) * c[j + 1];//减去超过限制的硬币的价值
				}
			}

			//容斥定理 奇加偶减
			if (sum >= 0) res += (cnt % 2 * 2 - 1) * f[sum];
		}

		cout << f[s] - res << endl;//总数-不合法数
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/XZ_Acmer/article/details/121363040
今日推荐