传送门
题目描述
硬币购物一共有4种硬币。面值分别为c1,c2,c3,c4。某人去商店买东西,去了tot次。每次带di枚ci硬币,买si的价值的东西。请问每次有多少种付款方法。
输入格式
第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,s
输出格式
每次的方法数
输入输出样例
输入 #1
1 2 5 10 2
3 2 3 1 10
1000 2 2 2 900
输出 #1
4
27
说明/提示
di,s<=100000 tot<=1000
解答
根据容斥原理
\[ \left|\bigcap_{i=1}^n \overline{S_i}\right| = |U| - \left|\bigcup_{i=1}^n S_i\right| = \sum_{0 \le k\le n}(-1)^k\sum_{1\le i_1<\cdots
其中\(S_i\)表示第\(i\)种元素超限的取的方法集合,交的初始值是\(U\)。(\(U\)表全集,\(\overline A\)表\(A\)在\(U\)下的补集)
如何求出\(\left|\bigcap_{j=1}^k S_{i_j}\right|\)?
考虑先求出完全背包的dp值
然后强制将第\(i_j\)个元素选取超过\(d_{i_j}\)个。
这样的方案总数为\(dp[t]-dp[t-\sum_{j=1}^k(d_i+1)c_i]\)。(\(dp[]\)的负数项为\(0\))
然后就可以愉快地容斥了。
#include
using namespace std;
const int n = 4, mx = 1e5+10, pm[] = {1,-1};
#define int long long
int c[n], d[n], dp[mx] = {1};
signed main() {
for (int i = 0; i < 4; ++i) {
cin >> c[i];
for (int j = c[i]; j < mx; ++j)
dp[j] += dp[j-c[i]];
}
int tot, s;
cin >> tot;
while (tot--) {
for (int i = 0; i < 4; ++i) cin >> d[i];
cin >> s;
int res = 0;
for (int i = 0; i < 16; ++i) {
int tmp = s, cnt = 0;
for (int j = 0; j < 4; ++j) {
if ((i>>j) & 1) {
cnt++;
tmp -= (d[j]+1)*c[j];
}
}
// cout << cnt << ' ' << tmp << endl;
res += pm[cnt%2]*(tmp>=0?dp[tmp]:0);
}
cout << res << endl;
}
}