[HAOI2008]硬币购物

做题时间:2022.8.4

\(【题目描述】\)

\(4\) 中硬币,每一种面值为 \(c_i(c_i\leq 10^5)\) ,某人去了 \(n(n\leq 10^5)\) 次商店买东西,每次购买他带了 \(d_i(d_i\leq 10^5)\) 枚面值为 \(c_i\) 的硬币,想购买价值为 \(S(S\leq 10^5)\) 的物品,问有多少种付款方式。

\(【输入格式】\)

第一行五个整数, 表示 \(4\) 种硬币面值和 \(n\)
接下来 \(n\) 行,每行五个整数表示 \(4\) 种硬币的数量和要买的东西的面值

\(【输出格式】\)

每行一个整数表示答案。

\(【考点】\)

容斥、背包

【做法】

直接DP肯定不太行,翻译一下题目条件,相当于求解不定方程:

\[\sum\limits_{i=1}^4 c_i\cdot x_i=S,c_i\leq d_i \]

若没有限制,那就是一个典型的完全背包求解。带上限制后,相当于要求同时满足所有限制,直接求解不好做,使用容斥(具体见:容斥原理之带限制的不定方程求解 ),最后去除限制得到:

\[\sum\limits_{i=1}^4 c_i\cdot x_i=S-\sum\limits_{i=1}^k c_i\cdot (b_{a_i}+1) \]

其中 \(A_i\) 是一个 \(k\) 元集合,且 \(A_i=\{x|x\in \N_+,1\leq x\leq n\}\)

然后用完全背包求解即可。

具体实现的时候,预处理出全集 \(|U|\) ,即不带任何限制的方案。对于每一次购买,用二进制的方法枚举 \(a_i\) ,同时根据其 \(1\) 的个数判断系数是正是负。

\(【代码】\)

#include<cstdio>
#include<iomanip>
using namespace std;
typedef long long ll;//容斥原理的题一般都要long long
const int N=1e5+50;
int n,c[5],d[5],S;
ll f[N];
int main()
{
	for(int i=1;i<=4;i++) scanf("%d",&c[i]);
	scanf("%d",&n);
	f[0]=1;
	for(int i=1;i<=4;i++){
		for(int j=1;j<N;j++) if(j>=c[i]) f[j]+=f[j-c[i]];
	}//完全背包预处理
    
	while(n--){
		for(int i=1;i<=4;i++) scanf("%d",&d[i]);
		scanf("%d",&S);
		ll ans=0;
		for(int k=1;k<(1<<4);k++){//用二进制下k的第i位表示A中是否包含i
			int bit=0;
			ll cnt=0;
			for(int i=1;i<=4;i++){
				if((1<<i-1)&k){//若k的第i位为1,表示当前这一项包含i
					cnt+=(d[i]+1)*c[i];
					bit++;
				}
			}
			if(S-cnt>=0) ans+=(bit%2==0 ? (-1) : 1) * f[S-cnt];//容斥公式的系数
		}
		printf("%lld\n",f[S]-ans);//全集-补集的并
	}
	return 0;
}

posted @ 2022-08-05 11:28  lxzy  阅读(49)  评论(0)    收藏  举报