题目链接: https://www.luogu.com.cn/problem/P1450
题意:
共有 4 种硬币。面值分别为 c1,c2,c3,c4
某人去商店买东西,去了 n 次,对于每次购买,他带了 di 枚 i 种硬币,想购买 s 的价值的东西。请问每次有多少种付款方法。
输入格式
输入的第一行是五个整数,分别代表 c1,c2,c3,c4,n 范围: M = 1e5
接下来 n (n <= 1000) 行,每行有五个整数,描述一次购买,分别代表 d1,d2,d3,d4,s
解题思路:
首先, 题意很容易让人想到多重背包模型, 但是由于有 n 次询问, 所以时间复杂度不可取
如果硬币的数量没有限制就好了, 假如没有限制, 那么最后算出来的方案数就会多, 那其实减去多去的方案数就行了, 多的方案数的情况为: 一种硬币数量超过限制, 两种…四种, 他们之间两两组合, 最多有 16 种组合情况, 并且发现两种硬币数量超过限制的情况是包含一种情况的, 所以这里很容易想到容斥原理来算出不符合限制的情况的
所以最后的方法就是: 预处理完全背包的方案数 (注意和完全背包模板不同), 然后运用容斥原理来减去硬币超过限制方案数 复杂度O(M)
代码:
public class Main {
private static final int N = (int)(1e5 + 5);
private static long f[] = new long[N];
private static long c[] = new long[10];
private static long d[] = new long[10];
private static int s;
private static long ans = 0;
public static void main(String[] args) {
InputReader in = new InputReader();
PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));
for(int i = 1; i <= 4; i++) {
c[i] = Integer.parseInt(in.next());
}
int n = Integer.parseInt(in.next());
f[0] = 1;
// 完全背包方案数求法 和 完全背包模板有不同
for(int i = 1; i <= 4; i++) {
for(int j = (int)c[i]; j < N; j++) {
f[j] += f[j - (int)c[i]];
}
}
while(n-- > 0) {
for(int i = 1; i <= 4; i++) {
d[i] = Integer.parseInt(in.next());
}
s = Integer.parseInt(in.next());
ans = f[s];
// 容斥原理 二进制枚举组合
for(int i = 1; i <= 15; i++) {
int now = s, k;
for(int tmp = i, j = k = 1; tmp > 0; tmp >>= 1, j++) {
if((tmp & 1) == 1) {
// 通过 k 来判定是加还是减
k ^= 1;
now -= (d[j] + 1) * c[j];
}
}
if(now >= 0) {
if(k > 0) {
ans += f[now];
} else {
ans -= f[now];
}
}
}
out.println(ans);
out.flush();
}
}
static class InputReader {
BufferedReader br = null;
StringTokenizer tok = null;
InputReader() {
br = new BufferedReader(new InputStreamReader(System.in));
}
boolean hasNext() {
while(tok == null || !tok.hasMoreElements()) {
try{
tok = new StringTokenizer(br.readLine());
} catch (Exception e) {
return false;
}
}
return true;
}
String next() {
return hasNext() ? tok.nextToken() : null;
}
}
}