Luogu 1450 [HAOI2008]硬币购物

优美的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;
}
View Code

 

posted @ 2018-08-26 12:00  CzxingcHen  阅读(99)  评论(0编辑  收藏  举报