P1450 [HAOI2008]硬币购物(完全背包+容斥)
暴力做法:每次询问跑一遍多重背包。
考虑正解
其实每次跑多重背包都有一部分是被重复算的,浪费了大量时间
考虑先做一遍完全背包
算出$f[i]$表示买价值$i$东西的方案数
蓝后对每次询问价值$t$,减去不合法的方案
$c_1$超额方案$f[t-c_1*(d_1+1)]$,表示取了$d_1+1$个$c_1$,剩下随便取的方案数(这就是差分数组)
如法炮制,减去$c_2,c_3,c_4$的超额方案数
但是我们发现,我们多减了$(c_1,c_2),(c_1,c_3),(c_1,c_4)......$同时超额的方案数
于是就再把方案数$f[t-c_i*(d_i+1)-c_j*(d_j+1)]$给加回来
然鹅我们又多加上了$(c_1,c_2,c_3)....$3种硬币同时超额的方案数,于是又要减掉这些方案
最后再把4种硬币都超额的方案数加回来
这就是容斥辣
#include<iostream> #include<cstdio> #include<cstring> using namespace std; typedef long long ll; ll ans,f[100005]; int c[5],d[5],T,t; int main(){ scanf("%d%d%d%d%d",&c[0],&c[1],&c[2],&c[3],&T); f[0]=1; for(int i=0;i<=3;i++) for(int j=c[i];j<=100000;++j) f[j]+=f[j-c[i]];//预处理 while(T--){ scanf("%d%d%d%d%d",&d[0],&d[1],&d[2],&d[3],&t); ans=f[t]; for(int i=1;i<16;++i){//二进制枚举子集 ll now=t,k=1; for(int j=0;j<=3;++j) if((i&(1<<j))) k=-k,now-=c[j]*(d[j]+1); if(now>=0) ans+=k*f[now]; }printf("%lld\n",ans); }return 0; }