优美的dp + 容斥。
首先可以不用考虑数量限制,处理一个完全背包$f_{i}$表示用四种面值的硬币购买的方案数,对于每一个询问,我们考虑容斥。
我们的$f_{s}$其实多包含了$f_{s - c_{i} * (d_{i} + 1)}$,所以我们把这些减去(这个式子的意思可以看成把$d_{i} + 1$以上的数全部都删掉做一个完全背包,就是只选$d_{i}$个),然而这样多减掉了同时选择两个的,又多加了同时选择三个的……
写成位运算就很优美啦。
时间复杂度$O(maxS + |s| * 2^{|s|} * tot)$。
Code:
#include <cstdio> #include <cstring> using namespace std; typedef long long ll; const int N = 1e5 + 5; int testCase, c[5], d[5]; ll f[N]; template <typename T> inline void read(T &X) { X = 0; char ch = 0; T op = 1; for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } int main() { for(int i = 1; i <= 4; i++) read(c[i]); f[0] = 1; for(int i = 1; i <= 4; i++) for(int j = c[i]; j <= 100000; j++) f[j] += f[j - c[i]]; /* for(int i = 0; i <= 20; i++) printf("%lld ", f[i]); printf("\n"); */ for(read(testCase); testCase--; ) { for(int i = 1; i <= 4; i++) read(d[i]); int s; read(s); ll res = 0; for(int i = 0; i <= 15; i++) { ll tot = s; bool flag = 0; for(int j = 0; j < 4; j++) if(i & (1 << j)) flag ^= 1, tot -= c[j + 1] * (d[j + 1] + 1); if(tot < 0) continue; if(flag) res -= f[tot]; else res += f[tot]; } printf("%lld\n", res); } return 0; }