1. 程式人生 > >P1450 [HAOI2008]硬幣購物(完全背包+容斥)

P1450 [HAOI2008]硬幣購物(完全背包+容斥)

額的 完全背包 while typedef 方案 problem iostream ios 這就是

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

P1450 [HAOI2008]硬幣購物(完全背包+容斥)