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