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;
}

posted @ 2018-08-14 22:13  谜のNOIP  阅读(96)  评论(0编辑  收藏  举报