首页 > 代码库 > BZOJ 1042 HAOI 2008 硬币购物 容斥原理

BZOJ 1042 HAOI 2008 硬币购物 容斥原理

题目大意:给出4个硬币的价值和个数限制,求有多少种方法凑成S块钱。


思路:很巧妙的一种想法,用到了4这个非常小的数字。我们可以先不管每个硬币的个数限制,然后跑一次完全背包。之后把不符合的情况去除就行了。方法是,先减去一种硬币超限的数目,然后加上两种硬币超限的数目,然后减去三种硬币超限的数目,然后加上四种硬币超限的个数。当然代码就很丑了。。


CODE:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAX 100010
#define P(i) (c[i] * (l[i] + 1))
using namespace std;
 
int c[5],l[5],s;
int cnt;
long long f[MAX],ans;
 
int main()
{
    for(int i = 1; i <= 4; ++i)  
        scanf("%d",&c[i]);
    cin >> cnt;
    for(int i = 1; i <= cnt; ++i) {
        for(int j = 1; j <= 4; ++j)
            scanf("%d",&l[j]);
        scanf("%d",&s);
        memset(f,0,sizeof(f));
        f[0] = 1;
        for(int _i = 1; _i <= 4; ++_i)
            for(int j = c[_i]; j <= s; ++j)
                f[j] += f[j - c[_i]];       
        ans = f[s];
        for(int _i = 1; _i <= 4; ++_i)   
            if(s - P(_i) >= 0)
                ans -= f[s - P(_i)];
        if(s - P(1) - P(2) >= 0) ans += f[s - P(1) - P(2)];
        if(s - P(2) - P(3) >= 0) ans += f[s - P(2) - P(3)];
        if(s - P(3) - P(4) >= 0) ans += f[s - P(3) - P(4)];
        if(s - P(2) - P(4) >= 0) ans += f[s - P(2) - P(4)];
        if(s - P(1) - P(3) >= 0) ans += f[s - P(1) - P(3)];
        if(s - P(1) - P(4) >= 0) ans += f[s - P(1) - P(4)];
        if(s - P(1) - P(2) - P(3) >= 0)  ans -= f[s - P(1) - P(2) - P(3)];
        if(s - P(2) - P(3) - P(4) >= 0)  ans -= f[s - P(2) - P(3) - P(4)];
        if(s - P(1) - P(3) - P(4) >= 0)  ans -= f[s - P(1) - P(3) - P(4)];
        if(s - P(1) - P(2) - P(4) >= 0)  ans -= f[s - P(1) - P(2) - P(4)];
        if(s - P(1) - P(2) - P(3) - P(4) >= 0)   ans += f[s - P(1) - P(2) - P(3) - P(4)];
        printf("%lld\n",ans);
    }
    return 0;
}


BZOJ 1042 HAOI 2008 硬币购物 容斥原理