【HAOI 2008】硬币购物

本文探讨了一种购物支付场景下的硬币组合问题,通过完全背包算法结合容斥原理解决多重限制下的付款方式计算。利用位运算优化计算过程,实现了对大量数据的有效处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

硬币购物一共有 444 种硬币。面值分别为 c1,c2,c3,c4c_1,c_2,c_3,c_4c1,c2,c3,c4。某人去商店买东西,去了 tottottot 次。每次带 dijd_{ij}dijcijc_{ij}cij 硬币,买 sis_isi 的价值的东西。请问每次有多少种付款方法。di,s≤100000d_i,s\le 100000di,s100000tot≤1000tot\le 1000tot1000

算法分析

并不会多重背包,有空再去学,然而多重背包貌似也不能做。

先考虑没有限制的情况,就是一个完全背包,设已计算出的用前 nnn 种硬币购买价值 iii 的物品的方案数为 f[i]f[i]f[i],则仅给第 jjj 种硬币加上选取个数满足 [0,di,j][0,d_{i,j}][0,di,j] 限制时的方案数为 f[i]−f[i−c[j]×(d[j]+1)]f[i]-f[i-c[j]\times(d[j]+1)]f[i]f[ic[j]×(d[j]+1)]

为什么呢?给第 jjj 种硬币加上这种限制后使用该种硬币的数量不得超过 d[j]d[j]d[j],考虑补集转化,我们可以先让它先不使用 d[j]+1d[j]+1d[j]+1 枚该种硬币,最后再使用 d[j]+1d[j]+1d[j]+1 枚该种金币,即 f[i−c[j]×(d[j]+1)]f[i-c[j]\times(d[j]+1)]f[ic[j]×(d[j]+1)],这样无论之前怎么选都能保证选择的硬币个数大于 d[i]d[i]d[i],最后再用总的方案数减去不满足条件的方案数就是给第 jjj 种硬币加上限制的方案数。

注意到,如果我们给多种硬币加上限制,可能会有重复减去的部分,即该方案既满足使用硬币 xxx 的数量大于 d[x]d[x]d[x] 又满足使用硬币 yyy 的数量大于 d[y]d[y]d[y],它的方案数等于 f[i−c[x]×(d[x]+1)−c[y]×(d[y]+1)]f[i-c[x]\times(d[x]+1)-c[y]\times(d[y]+1)]f[ic[x]×(d[x]+1)c[y]×(d[y]+1)],将多减的部分加回去就可以了。这实际上使用的是容斥原理,可以用位运算的方法方便的计算出来。

注意要开 646464 位整数保存结果。

代码实现

#include <cstdio>
typedef long long int ll;
int c[4],d[4];ll f[100005],sub[4];
int main() {
	f[0]=1;
	for(int i=0;i<4;++i) {
		scanf("%d",&c[i]);
		for(int j=c[i];j<=100000;++j) f[j]+=f[j-c[i]];
	}
	int tot;scanf("%d",&tot);
	while(tot--) {
		for(int i=0;i<4;++i) {
			scanf("%d",&d[i]);
			sub[i]=c[i]*(d[i]+1LL);
		}
		int s;scanf("%d",&s);ll ans=0;
		for(int i=0;i<(1<<4);++i) {
			int cnt=0;ll t=0;
			for(int j=0;j<4;++j) if(i>>j&1) {
				++cnt;t+=sub[j];
			}
			if(s-t>=0) (cnt&1)?ans-=f[s-t]:ans+=f[s-t];
		}
		printf("%lld\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值