[HAOI2008]硬币购物

link

一开始以为是一道智商税题目,结果发现似乎并没有那么简单。这道题给我提供了许多很有价值的思路。

首先多重背包是肯定会死的,二进制拆分似乎也并不是很有用处 (\(O(QN\log N)\)肯定超时),于是想到整体减空白。

整体是什么?显然会是没有限制,也就是所有硬币随便用时的方案数,此时直接暴力完全背包预处理即可。那空白呢?

空白的意义可以认为是强制硬币使用超量的情况。既然强制硬币超量,那么大可以先把超量硬币的面额减去,那么剩下的钱不管怎么花最后整体结果都是不合法的,而后者的计算就转换到了整体部分了,于是解决问题就只差个容斥了。可以枚举1到16,然后1的个数判加减即可,基本容斥。

#include<cstdio>
#define zczc
#define int long long
const int N=100010;
inline void read(int &wh){
    wh=0;int f=1;char w=getchar();
    while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
    while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
    wh*=f;return;
}

int m,n,now,ans,num,s[N],a[5],b[5];

signed main(){
	
	#ifdef zczc
	freopen("in.txt","r",stdin);
	#endif
	
	s[0]=1;
	for(int i=1;i<=4;i++){
		read(a[i]);
		for(int j=a[i];j<N;j++)s[j]+=s[j-a[i]];
	}
	read(m);
	while(m--){
		for(int i=1;i<=4;i++)read(b[i]);
		ans=0;read(n);
		for(int i=0;i<16;i++){
			now=n,num=0;
			for(int j=1;j<=4;j++){
				if(i&(1<<j-1)){
					now-=a[j]*(b[j]+1);
					num++;
				}
			}
			if(now>=0)ans+=((num&1)?-1:1)*s[now];
		}
		printf("%lld\n",ans);
	}
	
	return 0;
}
posted @ 2022-05-16 22:24  Feyn618  阅读(20)  评论(0编辑  收藏  举报