HAOI2008 硬币购物
据说NOIP前写题解会\(\mathcal{RP}\)++
看数据范围,肯定不能写多重背包,会\(T\)飞~
如果每种硬币没有个数限制,就可以用完全背包了。
正难则反,我们可以先用完全背包预处理,然后减去不合法的情况。不合法的情况就是一个\(s-(d+1) \times c\)的背包
但如果我们直接减去,会导致重复计算。比如我们减去第一种超的和第二种超的,第一种和第二种都超的就被减了两次。所以要容斥
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
long long ans,f[100010],c[5],d[5];
int read(){
int k=0; char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9')
k=k*10+c-48,c=getchar();
return k;
}
int main(){
for(int i=0;i<=3;i++) c[i]=read();
f[0]=1LL; //完全背包预处理
for(int i=0;i<=3;i++)
for(int j=c[i];j<=100001;j++)
f[j]+=f[j-c[i]];
int t=read();
while(t--){
for(int i=0;i<=3;i++) d[i]=read();
int s=read(); ans=f[s];
for(int i=1;i<=15;i++){ //用子集枚举来容斥
int flag=0,num=s;
for(int j=0;j<=3;j++)
if(i&(1<<j))
num-=(d[j]+1)*c[j],++flag; //减去不合法情况
if(num>=0) //容斥:奇减偶加
if(flag&1) ans-=f[num];
else ans+=f[num];
}
cout<<ans<<endl;
}
return 0;
}