每种硬币有数量限制,感觉很不好算
先考虑一下如果没用限制时可以怎么做
显然直接背包一下就可以了
设 $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 ",ans); } return 0; }