【题目大意】
有四种面值的硬币$c[1~4]$,现在要买$tot$次东西,每次买东西会分别带$d[1~4]$个四种面值的硬币,要买的东西总价格为$s$,求每次刚好买下这个东西(即用掉的硬币面值总和恰好为$s$)的方案数。
【思路分析】
首先,如果不存在硬币数量的限制,我们很容易想到要用完全背包。于是我们预处理出没有硬币数量限制的情况下,对于每个总价的方案数。
然后思考如何处理有数量限制的情况,假设只有一种硬币有数量限制,那么强制这种硬币超出限制,此时我们要用其他面值的硬币拼出的价值为$s-(d+1)*c$。因为超出限制,所以显然这些方案是不合法的,方案数为$f[s-(d+1)*c]$。于是我们成功知道了有一种硬币数量超出限制的方案数(此处不保证只有这一种硬币超出限制),用$f[s]$减去所有一种硬币数量超出限制的方案数之后,我们可以想到这其中多减去了两种硬币数量同时超出限制的方案数。到这里是不是有点眼熟?很显然可以发现这题要用容斥解决,所以最后的答案应该为:
$f[s]-$一种硬币数量超出限制的方案$+$两种硬币数量超出限制的方案$-$三种硬币数量超出限制的方案$+$四种硬币数量超出限制的方案。
这样就$over$啦!
【代码实现】
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<queue> 7 #define g() getchar() 8 #define rg register 9 #define go(i,a,b) for(rg int i=a;i<=b;i++) 10 #define back(i,a,b) for(rg int i=a;i>=b;i--) 11 #define db double 12 #define ll long long 13 #define il inline 14 #define pf printf 15 #define mem(a,b) memset(a,b,sizeof(a)) 16 using namespace std; 17 int fr(){ 18 int w=0,q=1; 19 char ch=g(); 20 while(ch<'0'||ch>'9'){ 21 if(ch=='-') q=-1; 22 ch=g(); 23 } 24 while(ch>='0'&&ch<='9') w=(w<<1)+(w<<3)+ch-'0',ch=g(); 25 return w*q; 26 } 27 int c[5],d[5],tot,s; 28 ll f[100002],ans; 29 int main(){ 30 //freopen("","r",stdin); 31 //freopen("","w",stdout); 32 go(i,1,4) c[i]=fr();tot=fr(); 33 f[0]=1; 34 go(i,1,4) go(j,c[i],100000) f[j]+=f[j-c[i]]; 35 while(tot--){ 36 ans=0; 37 go(i,1,4) d[i]=fr();s=fr(); 38 go(i,0,15){//2^4=16 39 int t=s,type=0; 40 go(j,1,4) if((i>>(j-1))&1) t-=c[j]*(d[j]+1),type^=1;//容斥的位运算写法 41 //当前位为1表示强制超出限制 42 if(t<0) continue; 43 if(!type) ans+=f[t];//偶数种硬币超出限制要加上方案 44 else ans-=f[t];//奇数种硬币超出限制要减去方案 45 } 46 pf("%lld ",ans); 47 } 48 return 0; 49 }