Luogu P1450 硬币购物 题解报告

题目传送门

【题目大意】

有四种面值的硬币$c[1~4]$,现在要买$tot$次东西,每次买东西会分别带$d[1~4]$个四种面值的硬币,要买的东西总价格为$s$,求每次刚好买下这个东西(即用掉的硬币面值总和恰好为$s$)的方案数。

【思路分析】

首先,如果不存在硬币数量的限制,我们很容易想到要用完全背包。于是我们预处理出没有硬币数量限制的情况下,对于每个总价的方案数。

然后思考如何处理有数量限制的情况,假设只有一种硬币有数量限制,那么强制这种硬币超出限制,此时我们要用其他面值的硬币拼出的价值为$s-(d+1)*c$。因为超出限制,所以显然这些方案是不合法的,方案数为$f[s-(d+1)*c]$。于是我们成功知道了有一种硬币数量超出限制的方案数(此处不保证只有这一种硬币超出限制),用$f[s]$减去所有一种硬币数量超出限制的方案数之后,我们可以想到这其中多减去了两种硬币数量同时超出限制的方案数。到这里是不是有点眼熟?很显然可以发现这题要用容斥解决,所以最后的答案应该为:

$f[s]-$一种硬币数量超出限制的方案$+$两种硬币数量超出限制的方案$-$三种硬币数量超出限制的方案$+$四种硬币数量超出限制的方案。

这样就$over$啦!

【代码实现】

 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\n",ans);
47     }
48     return 0;
49 }
代码戳这里
posted @ 2019-09-26 16:46  小叽居biubiu  阅读(212)  评论(0编辑  收藏  举报