bzoj 1042 [HAOI2008]硬币购物 容斥

题面

题目传送门

解法

挺妙的一道题

显然不能每一次都多重背包,效率太低

发现物品种类只有4种,可以干点什么

\(f_i\)表示所有物品都无限取的时候的方案数

这个可以通过无限背包处理出来

对于每一次询问,答案即为得到面值S的超过限制的方案数 – 第1种硬币超过限制的方案数 – 第2种硬币超过限制的方案数 – 第3种硬币超过限制的方案数 – 第4种硬币超过限制的方案数 + 第1,2种硬币同时超过限制的方案数 + 第1,3种硬币同时超过限制的方案数 + …… + 第1,2,3,4种硬币全部同时超过限制的方案数

假设第一种超过了限制,那么方案数为\(f_{i-c_i×(d_i+1)}\)

时间复杂度:\(O(s+2^4×T)\)

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
template <typename node> void chkmax(node &x, node y) {x = max(x, y);}
template <typename node> void chkmin(node &x, node y) {x = min(x, y);}
template <typename node> void read(node &x) {
    x = 0; int f = 1; char c = getchar();
    while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
int c[4], d[4], f[100010];
main() {
    for (int i = 0; i < 4; i++) read(c[i]); f[0] = 1;
    for (int i = 0; i < 4; i++)
        for (int j = c[i]; j <= 1e5; j++)
            f[j] += f[j - c[i]];
    int T; read(T);
    while (T--) {
        for (int i = 0; i < 4; i++) read(d[i]);
        int s; read(s); int ans = 0;
        for (int i = 0; i <= 15; i++) {
            int now = s, k = 1;
            for (int j = 0; j < 4; j++)
                if ((i >> j) & 1) now -= c[j] * (d[j] + 1), k ^= 1;
            if (now < 0) continue;
            if (k) ans += f[now]; else ans -= f[now];
        }
        cout << ans << "\n";
    }
    return 0;
}
posted @ 2018-08-14 22:13  谜のNOIP  阅读(...)  评论(... 编辑 收藏