P1450 [HAOI2008]硬币购物(完全背包+容斥)

时间:2021-08-16 18:08:08

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[];
int c[],d[],T,t;
int main(){
scanf("%d%d%d%d%d",&c[],&c[],&c[],&c[],&T);
f[]=;
for(int i=;i<=;i++)
for(int j=c[i];j<=;++j)
f[j]+=f[j-c[i]];//预处理
while(T--){
scanf("%d%d%d%d%d",&d[],&d[],&d[],&d[],&t);
ans=f[t];
for(int i=;i<;++i){//二进制枚举子集
ll now=t,k=;
for(int j=;j<=;++j)
if((i&(<<j)))
k=-k,now-=c[j]*(d[j]+);
if(now>=) ans+=k*f[now];
}printf("%lld\n",ans);
}return ;
}