P1450 [HAOI2008]硬币购物

Label

基本多重集组合数问题+完全背包

Description

给定4种面值的硬币,第 i i i种的面值是 C i C_i Ci n ( n ≤ 1 0 3 ) n(n\le 10^3) n(n103)次询问,每次询问给出每种硬币的持有数量 D i D_i Di与价格 S ( S ≤ 1 0 5 ) S(S\le 10^5) S(S105),问用给出硬币付款的方案总数。

solution

若用分组背包做此题,复杂度为 O ( 4 n S ) O(4nS) O(4nS),实际运行时会超时,故考虑复杂度更低的解法。

借用求多重集组合数的思路,原问题即转化为:给定不定方程 ∑ i = 1 4 C i x i = S \sum_{i=1}^4C_ix_i=S i=14Cixi=S 4 4 4个约束条件 x i ≤ D i x_i\le D_i xiDi,求该不定方程的非负整数解的个数。

采用同样的容斥方式,最后每组询问便转化为求 2 4 − 1 2^4-1 241次无限制的不定方程 ∑ i = 1 4 C i x i = S − ∑ i = 1 4 C a i ( D a i + 1 ) \sum_{i=1}^4C_ix_i=S-\sum_{i=1}^{4}C_{a_i}(D_{a_i}+1) i=14Cixi=Si=14Cai(Dai+1)的值。

与原问题不同的是,此时求 ∣ ⋂ i = 1 k ∁ U S a i ∣ |\bigcap_{i=1}^{k}\complement_US_{a_i}| i=1kUSai无法利用排列组合直接推通式,但其实求 ∑ i = 1 4 C i x i = S − ∑ i = 1 4 C a i ( D a i + 1 ) \sum_{i=1}^4C_ix_i=S-\sum_{i=1}^{4}C_{a_i}(D_{a_i}+1) i=14Cixi=Si=14Cai(Dai+1)的解的个数即为用***所有种类***的无限个 C i C_i Ci***恰好***装满容量为 S − ∑ i = 1 4 C a i ( D a i + 1 ) S-\sum_{i=1}^{4}C_{a_i}(D_{a_i}+1) Si=14Cai(Dai+1)的背包的方案数。

我们利用完全背包 O ( 4 n ) O(4n) O(4n)预处理出利用不限制个数的四种硬币凑得值为 n n n的方案数 f [ n ] f[n] f[n],对于每一组询问,答案即为:
f [ n ] + ∑ k = 1 n ( − 1 ) k ∑ a i < a i + 1 ∣ a ∣ = k f [ S − ∑ i = 1 k C a i ( D a i + 1 ) ] f[n]+\sum_{k=1}^{n}(-1)^k\sum_{a_i<a_{i+1}}^{|a|=k}f[S-\sum_{i=1}^kC_{a_i}(D_{a_i}+1)] f[n]+k=1n(1)kai<ai+1a=kf[Si=1kCai(Dai+1)]

对于每组询问只需 O ( 2 4 ) O(2^4) O(24)求解即可。算法总复杂度 O ( 4 n + 2 4 n ) O(4n+2^4n) O(4n+24n)

Code

#include<cstdio>
#include<iostream>
#define ri register int
#define ll long long
using namespace std;

const int MAXN=1e5;
int N=4,c[5],T,d[5],S,tot,cnt,use[5];
ll f[MAXN+20],ans;

void dp()
{
	f[0]=1;
	for(ri i=1;i<=4;++i)
		for(ri v=c[i];v<=MAXN;++v) f[v]+=f[v-c[i]];
}

void inex()
{
	for(ri i=1;i<=N;++i) scanf("%d",&d[i]);
	scanf("%d",&S);
	ans=f[S];
	for(ri s=1;s<(1<<N);++s)
	{
		cnt=0,tot=0;
		for(ri i=1;i<=N;++i)
			if(s&(1<<(i-1)))
				++cnt,tot+=(d[i]+1)*c[i];
		if(S-tot<0) continue;
		if(cnt&1) ans-=f[S-tot];
		else ans+=f[S-tot];
	}
	cout<<ans<<'\n';
}

int main()
{
	for(ri i=1;i<=N;++i) scanf("%d",&c[i]);
	dp();
	scanf("%d",&T);
	for(ri i=1;i<=T;++i) inex();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值