bzoj 1042: [HAOI2008]硬币购物

题目链接

bzoj1042

题解

如果没有个数限制就是一个完全背包
考虑利用全集减去超出限制的种数
利用容斥
减去一种金币超出的,加上两种金币超出的,减去三种.......

\(f(S)\)只有 S种金币超出的方案数,\(g(S)\)为S中的金币超过方限制,其他随意的方案数
那么\(\sum_{T\supseteq S}f \left(T \right)\)
我们要求\(f\left( \emptyset \right)\)
求g(s),吧\(N\)中减去\(S\)中选了\(d_i+1\)个的和,然后剩下的就是一个完全背包
预处理后容斥查询

code

#include<cstdio>

inline int read() {
	int x=0,f=1;
	char c=getchar() ;
	while(c<'0'||c>'9') {
		 if(c=='-')f=-1;
		 c=getchar();
	}
	while(c<='9'&&c>='0') {
		x=x*10+c-'0';
		c=getchar();
	}
	return x*f;
}const int maxn = 100001;
int M[6],N[6];
int f[maxn];
int tot,ned;
void init() {
	f[0]=1;
	for(int i=1;i<=4;++i)
		for(int j=M[i];j<maxn;++j) 
			f[j]+=f[j-M[i]];
}
int ans;
void solve(int rem,int cnt,int num) {
	if(num==5) {
		if(!cnt)return ;
		if(rem<0)return ;
		if(cnt&1) ans-=f[rem];
		else ans+=f[rem];
		return ;
	}
	solve(rem-((N[num]+1)*M[num]),cnt+1,num+1);
	solve(rem,cnt,num+1);
}
int main() {
	for(int i=1;i<5;++i) M[i]=read();
	tot=read();
	init();
	for(;tot--;) {
		for(int i=1;i<5;++i) N[i]=read();
		ned=read();ans=f[ned];
		solve(ans,0,1);
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2018-02-03 11:17  zzzzx  阅读(177)  评论(0编辑  收藏  举报