bzoj 1042 [HAOI2008]硬币购物 容斥
题面
解法
挺妙的一道题
显然不能每一次都多重背包,效率太低
发现物品种类只有4种,可以干点什么
设\(f_i\)表示所有物品都无限取的时候的方案数
这个可以通过无限背包处理出来
对于每一次询问,答案即为得到面值S的超过限制的方案数 – 第1种硬币超过限制的方案数 – 第2种硬币超过限制的方案数 – 第3种硬币超过限制的方案数 – 第4种硬币超过限制的方案数 + 第1,2种硬币同时超过限制的方案数 + 第1,3种硬币同时超过限制的方案数 + …… + 第1,2,3,4种硬币全部同时超过限制的方案数
假设第一种超过了限制,那么方案数为\(f_{i-c_i×(d_i+1)}\)
时间复杂度:\(O(s+2^4×T)\)
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
template <typename node> void chkmax(node &x, node y) {x = max(x, y);}
template <typename node> void chkmin(node &x, node y) {x = min(x, y);}
template <typename node> void read(node &x) {
x = 0; int f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
int c[4], d[4], f[100010];
main() {
for (int i = 0; i < 4; i++) read(c[i]); f[0] = 1;
for (int i = 0; i < 4; i++)
for (int j = c[i]; j <= 1e5; j++)
f[j] += f[j - c[i]];
int T; read(T);
while (T--) {
for (int i = 0; i < 4; i++) read(d[i]);
int s; read(s); int ans = 0;
for (int i = 0; i <= 15; i++) {
int now = s, k = 1;
for (int j = 0; j < 4; j++)
if ((i >> j) & 1) now -= c[j] * (d[j] + 1), k ^= 1;
if (now < 0) continue;
if (k) ans += f[now]; else ans -= f[now];
}
cout << ans << "\n";
}
return 0;
}