P1450 [HAOI2008]硬币购物

传送门

每种硬币有数量限制,感觉很不好算

先考虑一下如果没用限制时可以怎么做

显然直接背包一下就可以了

设 $f[i][j]$ 表示前 $i$ 种硬币选了一些,总价值为 $j$ 的方案数,转移显然,并且可以滚动数组优化

但是现在有限制,考虑容斥,设 $f[j]$ 表示不考虑限制总价值为 $j$ 的方案

一开始答案为 $f[S]$,然后发现多算了,可能硬币不够的方案也算进去了,所有考虑把每一种硬币多出的情况减去,就是强制一种硬币超过限制,其他的顺便选,这个值可以用 $f$ 求出

然后发现减多了,把两种硬币多出的情况减了 $C^{1}_{2}$ 次 ,把三种硬币多出的情况减了 $C^{1}_{3}$ 次,把四种硬币多出的情况减了 $C^{1}_{4}$ 次

所以要再把所有两种硬币多出的情况加上来,那么每两种的情况加了 $C^{2}_{2}$ 次,然后每两种硬币多出的情况就刚好只被减了一次

但是还有点问题,我们又多加了每三种硬币的情况,加了 $C^{2}_{3}$ 次,那么现在三种硬币的情况就没被扣掉

并且发现四种硬币的情况也被多加了,加了 $C^{2}_{4}$ 次,此时四种硬币超过限制的情况反而多加了 $2$ 次

先考虑把三种硬币多出的情况扣掉,把每三种硬币的情况减去,

那么此时每三种硬币的情况刚好被减了 $1$ 次,但是每四种硬币超过限制的情况又被扣了 $C^{3}_{4}$ 次

此时四种硬币的情况总共被扣了 $2$ 次

那么最后再把四种硬币的情况加上一次就好了

总结一下,没有限制 - 一个硬币的限制 + 两个硬币的限制 - 三个硬币的限制 + 四个硬币的限制

这就是最基础的容斥了,用位运算很方便实现

具体看代码吧

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=1e5+7;
int c[7],d[7],n,m;
ll f[N],ans;
int main()
{
    for(int i=1;i<=4;i++) c[i]=read();
    n=read();
    f[0]=1;
    for(int i=1;i<=4;i++)
        for(int j=c[i];j<N;j++) f[j]+=f[j-c[i]];
    while(n--)
    {
        for(int i=1;i<=4;i++) d[i]=read();
        m=read(); ans=0;
        for(int i=0;i<(1<<4);i++)//枚举硬币的状态,是否强制超过限制
        {
            int cnt=0; ll t=0;//cnt统计一个硬币超过限制,t统计在i状态下最少要多少钱
            for(int j=0;j<4;j++) if((i>>j)&1) cnt++,t+=c[j+1]*(d[j+1]+1);
            if(t<=m)
                cnt&1 ? ans-=f[m-t] : ans+=f[m-t];//容斥
        }
        printf("%lld\n",ans);
    }
    return 0;
}

 

posted @ 2019-04-26 13:34  LLTYYC  阅读(267)  评论(0编辑  收藏  举报