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;
}
posted @ 2019-11-14 09:52  MorsLin  阅读(95)  评论(0编辑  收藏  举报