码迷,mamicode.com
首页 > 其他好文 > 详细

题解 P1450 【[HAOI2008]硬币购物】

时间:2020-07-05 10:49:57      阅读:75      评论:0      收藏:0      [点我收藏+]

标签:return   code   use   space   amp   std   状压   math   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|\bigcup_{i=1}^4 A_i\right| \]

而对于\(|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 \bigcap\ A_j\right| \]

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

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

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

\[\left|A_i \bigcap\ A_j\right|=F[S-C_i(D_i+1)-C_j(D_j+1)] \]

那么

\[\begin{aligned} ans&=F[s]-\left|\bigcup_{i=1}^4 A_i\right| \\ &=F[s]-\sum_{T \subseteq\{1,2,3,4\}} -1^{|T|-1}\left|\bigcap_{j \in T } A_{j}\right| \\ &=F[s]-(-1)^{|T|-1}F[S-\sum_{j \in T} C_{j}\left(D_{j}+1\right)]\\ &=F[s]+(-1)^{|T|}F[S-\sum_{j \in T} C_{j}\left(D_{j}+1\right)] \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]硬币购物】

标签:return   code   use   space   amp   std   状压   math   haoi2008   

原文地址:https://www.cnblogs.com/wxy-max/p/13237728.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!