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

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;
}

 

posted @ 2019-04-27 12:07  kafuuchino  阅读(180)  评论(0编辑  收藏  举报