题解 P1450 [HAOI2008]硬币购物

Posted wxy-max

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解 P1450 [HAOI2008]硬币购物相关的知识,希望对你有一定的参考价值。

[HAOI2008]硬币购物

共有四种硬币,其面值分别为(c_1,c_2,c_3,c_4)

(n)次询问,每次给定每种硬币的个数(D_i)和付款金额(S),问共有多少种付款方式

(n≤10^3,S≤10^5)

暴力做法

我们可以把问题看作做(n)次多重背包,用单调队列优化,最优的复杂度为(O(n4s))在这里不详细讨论

完全背包+容斥

对于每次询问,相当于给定四个限制,第(i)个限制即只能至多使用(i)个硬币支付。类似于错排,满足限制的方案数=全部方案数-不满足的方案数
(错排的容斥推导可见https://www.luogu.com.cn/blog/maxsama/zuhemath)
而对于全部的方案数,相当于做体积为(S)的完全背包,而(S)至多为(10^5),因此我们可以(O(4S))预处理所有(S)

而对于不满足条件的方案数

类似于错排,若不满足其中一个限制条件,一定是非法的,我们可以设(A_i)为第(i)种硬币不满足限制的方案集合。

那么不满足条件的方案数即

[left|igcup_{i=1}^4 A_i ight| ]

而对于(|A_i|),我们当前一共要支付(S)个硬币,而对于第(i)种只能支付(D_i)个,那么我们如果先把(D_i+1)个硬币付完剩下的支付方案(即从四种硬币中支付(S-C_i(D_i+1))的金额)是一定不合法的,因为第(i)个硬币至少需要选零个,而如果选(0)个,最终还是多付了一个。

因此,第(|A_i|=F[S-C_i(D_i+1)])

因为我们要求集合的并,因此,我们需要考虑容斥,而考虑容斥我们就需要求出集合的交

我们考虑任意最简单的情况:两个集合的交

[left|A_i igcap A_j ight| ]

我们从定义出发,(A_i)为第(i)种硬币不满足限制的方案集合,而(A_j)为第(j)种硬币不满足限制的方案集合。

而显然,同时满足(i)种硬币不满足限制与(j)种硬币不满足限制这样的性质的集合,为它们的交。

同样地,我们可以把他们两个的(D_i+1)都先支付出去,那么则有

[left|A_i igcap A_j ight|=F[S-C_i(D_i+1)-C_j(D_j+1)] ]

那么

[egin{aligned} ans&=F[s]-left|igcup_{i=1}^4 A_i ight| \ &=F[s]-sum_{T subseteq{1,2,3,4}} -1^{|T|-1}left|igcap_{j in T } A_{j} ight| \ &=F[s]-(-1)^{|T|-1}F[S-sum_{j in T} C_{j}left(D_{j}+1 ight)]\ &=F[s]+(-1)^{|T|}F[S-sum_{j in T} C_{j}left(D_{j}+1 ight)] end{aligned} ]

那么我们可以状压枚举(T)算出答案。

总的复杂度为(O(4S+16n))

代码:


#include <iostream>

using namespace std;

const int maxn = 10;

int c[maxn],d[maxn];

long long f[100010];

int main(){
	int n;
	cin >> c[1] >> c[2] >> c[3] >> c[4] >> n;
	f[0] = 1;
	for (int i = 1; i <= 4; i++){
		for (int j = c[i]; j <= 100010; j++){
			f[j] += f[j - c[i]];
		}
	}
	while(n--){
		int s;
		cin >> d[1] >> d[2] >> d[3] >> d[4] >> s;
		long long ans = f[s];
		for (int i = 0; i < 1 << 4; i++){
			long long sum = 0,cnt = 0;
			for (int j = 0; j < 4; j++){
				if (i >> j & 1){ 
					sum += c[j + 1] * (d[j + 1] + 1);
					cnt++;
				}
			}
			//cout << sum << endl;
			long long t = s - sum;
			if (cnt % 2 == 0 && t >= 0 && sum != 0) ans += f[s - sum];
			else if (t >= 0 && sum != 0) ans -= f[s - sum];
		}
		cout << ans << endl;
	}
	//system("PAUSE");
	return 0;
}




以上是关于题解 P1450 [HAOI2008]硬币购物的主要内容,如果未能解决你的问题,请参考以下文章

P1450 [HAOI2008]硬币购物

Luogu P1450 [HAOI2008]硬币购物

@@ P1450 [HAOI2008]硬币购物

洛谷P1450 [HAOI2008]硬币购物

洛谷P1450 [HAOI2008]硬币购物

P1450 [HAOI2008]硬币购物(完全背包+容斥)