「HAOI2008」硬币购物 题解

「HAOI2008」硬币购物 题解

题意

给出硬币个数和价值,问有多少种方案使总和为 \(S\)

题解

显然,可以用多重背包解决, 但是对于本题数据范围非常的大,显然是不可以通过的。

可以发现,本题只是完全背包多了一些约束, 完全背包可以选不限个,本题可以选有限个。

那就可以先求完全背包,再减去不合法的状态,减多了就加,加多了就减。

咦?这不是容斥吗?所以本题就是一个赤裸裸的容斥。

对于多组数据,可以先把 \(10^5\) 的完全背包全部算出来,什么时候用什么时候方便。

显然,可以想到用暴力求不合法的状态,简单来说就是 \(DFS\) 求得,由于 \(n\) 非常小, \(DFS\) 不会 \(T\) 掉。

考虑 \(DFS\) 的参数,可以设一个当前位置,选择的硬币数量和当前所有硬币的价值。

当前位置和总和判断边界条件,选择硬币数量决定是加还是减。

显然,为了让选择的都不合法,我们让所有的 \(d_i\) 都加一个一,这样, \(DFS\) 求得的方案就都不合法了!

Code

#include <bits/stdc++.h>

using i64 = long long;	
constexpr int N = 4;
constexpr int M = 100011;

i64 f[M], ans;
std::vector<int> d(N + 1);
std::vector<int> c(N + 1);

inline int p(int x) {return x & 1 ? -1 : 1;}
inline void dfs(int now, int things, int sum) {
	if (sum < 0) return;
	if (now == 4 + 1) {
		ans = ans + f[sum] * p(things);
		return;
	}
	dfs(now + 1, things + 1, sum - (d[now] + 1) * c[now]);
	dfs(now + 1, things, sum);
}

int main() {
	std::ios::sync_with_stdio(false);
	
	for (int i = 1; i <= N; i++) {
		std::cin >> c[i];
	}	
	
	f[0] = 1;
	for (int i = 1; i <= N; i++) {
		for (int j = c[i]; j < M; j++) {
			f[j] += f[j - c[i]];
		}
	}
	
	int m;
	for (std::cin >> m; ~--m; ) {
		for (int i = 1; i <= N; i++){
			std::cin >> d[i];
		}
		int S; std::cin >> S;
		
		ans = 0;
		dfs(1, 0, S);
		std::cout << ans << "\n"; 
		
	}

	return 0;
}
posted @ 2022-02-12 15:33  落花月朦胧  阅读(82)  评论(1编辑  收藏  举报