P1450 [HAOI2008]硬币购物
题目大意:
首先给出四种不同面值的硬币 第种硬币的面值为, n次询问,每次询问输入4种面值硬币的数量,和需要购买的物品价值s,求有多少种付款方式
题目分析:
本题如果单纯使用多重背包来做 是O(n * logd * s)的复杂度会超时
如果本题改成硬币没有数量限制,我们就可以直接利用完全背包跑出来复杂度为O(4*s) ,但是现在有限制这样跑出来会有不合法情况,所有就有这样一个思路就是 用总方案呢数量-不合法方案数量 就是合法的方案数量,现在假如有一种方案的我们最少用个硬币 那么该方案数其实就是 这就是不合法的方案数因为第一个硬币一定超过了限制,现在再复杂一些比如有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()
下边模型截取自: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;
}