[BZOJ 3129] [Sdoi2013] 方程 【容斥+组合数取模+中国剩余定理】

题目链接:BZOJ - 3129

题目分析

使用隔板法的思想,如果没有任何限制条件,那么方案数就是 C(m - 1, n - 1)。

如果有一个限制条件是 xi >= Ai ,那么我们就可以将 m 减去 Ai - 1 ,相当于将这一部分固定分给 xi,就转化为无限制的情况了。

如果有一些限制条件是 xi <= Ai 呢?直接来求就不行了,但是注意到这样的限制不超过 8 个,我们可以使用容斥原理来求。

考虑容斥:考虑哪些限制条件被违反了,也就是说,有哪些限制为 xi <= Ai 却是 xi > Ai,这样就转化为了 xi >= Ai 的限制条件。

那么我们就可以在 2^8 * T(求组合数) 的时间内求出答案了。

怎么求这个组合数呢?直接预处理阶乘的逆元是不可以的,因为模数不都是质数。

我们要将模数拆成一个个 pi^ai 这样的形式,使得它们两两之间互质,就可以分别求出答案,最后再用中国剩余定理组合起来。

中国剩余定理:如果有n个方程 x = xi (mod mi) ,M = m1 * m2 * .. * mn ,那么在 mod M 的意义下,方程组有一个唯一解。

x = sigma(Mi * Inv(Mi) * xi) % M ,其中 Mi = M / mi ,Inv(Mi)是Mi在mod mi意义下的逆元。

那么我们的问题就是,如何求出 C(n, m) % (p^a) 。

这里就需要用到“组合数取模”了,专门用来求解这种问题。

使用类似于快速阶乘的方法,将组合数中分数线上下的阶乘都拆成 e * p^f 的形式,然后 e 直接计算,f 分数线上下相减之后再计算。

怎么将 x! 拆成 e * p^f 呢?

假设我们要 mod 的数是 p^a ,那么我们需要预处理出 [1, p^a - 1] 中除去 p 的倍数的其余数的前缀积(类似阶乘少了 p 的倍数)。

然后我们知道 [1, x] 中包含 p 的数有 x / p 个,我们将这些数中都提取出 1 个 p,那么就获得了 p^(x/p),然后这 x / p 个数就变成了 [1, x/p],就可以递归下去。

其余的部分可以分段来求,分成 [1, p^a - 1], [p^a + 1, p^a + p^a - 1] ..... 这样,每一段的积都是一样的 (mod p^a 意义下),直接快速幂就可以了。

最后还会剩下一段 [1, x % (p^a)] ,也是直接预处理出的值。

这样这道题就做完了(呼~)。

另外注意的是,在写代码的时候,我求逆元使用欧拉定理但是确用错了。

欧拉定理:a^phi(b) = 1 (mod b) 条件:gcd(a, b) = 1

注意是 a^phi(b) 而不是 a^(b-1) !当 b 不是质数的时候就跪了!

代码

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdio>

using namespace std;

typedef long long LL;
typedef double LF;

const int MaxP = 10201 + 15, MaxN1 = 8 + 5;

int T, p, n, n1, n2, m, Top, Ans;
int A[MaxN1];

LL Temp;
LL Fac[10][MaxP], Pr[10], Pi[10], Pa[10], Phi_Pi[10], Mi[10], Inv_Mi[10], Xi[10];

LL Pow(LL a, LL b, LL Mod)
{
	LL ret, f;
	ret = 1; f = a;
	while (b)
	{
		if (b & 1)
		{
			ret *= f;
			ret %= Mod;
		}
		b >>= 1;
		f *= f;
		f %= Mod;
	}
	return ret;
}

void Prepare()
{
	int x, SqrtX;
	x = p; SqrtX = (int)sqrt((LF)x);
	Top = 0;
	for (int i = 2; i <= SqrtX; ++i)
	{
		if (x % i != 0) continue;
		Pr[++Top] = i;
		Pa[Top] = 0;
		Pi[Top] = 1;
		while (x % i == 0)
		{
			++Pa[Top];
			Pi[Top] *= i;
			x /= i;
		}
		Phi_Pi[Top] = Pi[Top] / Pr[Top] * (Pr[Top] - 1);
	}
	if (x > 1)
	{
		Pr[++Top] = x;
		Pa[Top] = 1;
		Pi[Top] = x;
		Phi_Pi[Top] = Pi[Top] - 1;
	}
	for (int i = 1; i <= Top; ++i)
	{
		Mi[i] = p / Pi[i];
		Inv_Mi[i] = Pow(Mi[i], Phi_Pi[i] - 1, Pi[i]);
		Fac[i][0] = 1;
		for (int j = 1; j < Pi[i]; ++j)
		{
			if (j % Pr[i] != 0) Fac[i][j] = Fac[i][j - 1] * j % Pi[i];
			else Fac[i][j] = Fac[i][j - 1];
		}
	}
}

struct ES
{
	LL e, f;
};

ES Calc(int x, int k)
{
	ES ret, tc;
	if (x < Pr[k])
	{
		ret.e = Fac[k][x];
		ret.f = 0;
		return ret;
	}
	ret.f = x / Pr[k];
	tc = Calc(x / Pr[k], k);
	ret.f += tc.f;
	ret.e = tc.e * Fac[k][x % Pi[k]] % Pi[k];
	ret.e = ret.e * Pow(Fac[k][Pi[k] - 1], x / Pi[k], Pi[k]) % Pi[k];
	return ret;
}

LL C(int x, int y, int k)
{
	LL ret;
	int pf;
	ES Ex, Ey, Exy;
	Ex = Calc(x, k);
	Ey = Calc(y, k);
	Exy = Calc(x - y, k);
	ret = Ex.e * Pow(Ey.e, Phi_Pi[k] - 1, Pi[k]) % Pi[k] * Pow(Exy.e, Phi_Pi[k] - 1, Pi[k]) % Pi[k];
	pf = Ex.f - Ey.f - Exy.f;
	if (pf >= Pa[k]) ret = 0;
	else ret = ret * Pow(Pr[k], pf, Pi[k]) % Pi[k];
	return ret;
}

int C(int x, int y)
{
	if (x == y) return 1;
	if (x < y) return 0;
	if (y == 0) return 1;
	LL ret = 0;
	for (int i = 1; i <= Top; ++i) Xi[i] = C(x, y, i);
	for (int i = 1; i <= Top; ++i)
	{
		ret += Xi[i] * Mi[i] % p * Inv_Mi[i] % p;
		ret %= p;
	}
	return (int)ret;
}

void DFS(int x, int Cnt, int Sum)
{
	if (x == n1)
	{
		if (Cnt & 1) Temp -= C(m - Sum - 1, n - 1);
		else Temp += C(m - Sum - 1, n - 1);
		Temp = (Temp % p + p) % p;
		return;
	}
	DFS(x + 1, Cnt, Sum);
	DFS(x + 1, Cnt + 1, Sum + A[x + 1]);
}

int Solve()
{
	Temp = 0;
	DFS(1, 0, 0);
	DFS(1, 1, A[1]);
	return (int)Temp;
}

int main()
{
	scanf("%d%d", &T, &p);
	Prepare();
	for (int Case = 1; Case <= T; ++Case)
	{
		scanf("%d%d%d%d", &n, &n1, &n2, &m);
		for (int i = 1; i <= n1; ++i) scanf("%d", &A[i]);
		int Num;
		for (int i = 1; i <= n2; ++i)
		{
			scanf("%d", &Num);
			m -= Num - 1;
		}
		if (n1 > 0) Ans = Solve();
		else Ans = C(m - 1, n - 1);
		printf("%d\n", Ans);
	}
	return 0;
}

  

时间: 2024-08-01 22:43:05

[BZOJ 3129] [Sdoi2013] 方程 【容斥+组合数取模+中国剩余定理】的相关文章

BZOJ 3129 [Sdoi2013]方程 不定方程解的个数+组合数取模

题意:链接 方法:不定方程解的个数+组合数取模 解析: 先看n1与n2的部分的限制. 对于后半部分的限制来说,我们直接减去An1+i?1就可以转化一下求正整数解. 但是前半部分呢? 跟上一道猴子那个很像. 所以我们容斥搞就行了. 但是这道题好像不好写的地方不在这? 这题TMD不就是礼物吗! 大组合数取模如何取? 请参见我<BZOJ 礼物>的题解. 另外吐槽题干 明明是X1+X2+-+Xn=m 并不是小于等于 代码: #include <cstdio> #include <cs

●BZOJ 3129 [Sdoi2013]方程

题链: http://www.lydsy.com/JudgeOnline/problem.php?id=3129 题解: 容斥,扩展Lucas,中国剩余定理 先看看不管限制,只需要每个位置都是正整数时的方案数的求法.假设有 N 个未知数,加起来的和为 M.转化一下问题变为:"小球分配" 有 M 个相同的小球,放在 N 个盒子里,且每个盒子至少有一个的方案数. 那么方案数为 ${C}_{M-1}^{N-1}$怎么理解这个式子呢?"插隔板法".使 M个小球放在一排,考虑

BZOJ 3129 SDOI2013 方程

如果没有限制,答案直接用隔板法C(m-1,n-1) 对于>=x的限制,我们直接在对应位置先放上x-1即可,即m=m-(x-1) 对于<=x的限制,由于限制很小我们可以利用容斥原理将它转化为上面的>=x的限制 即减去1个不满足的 加上2个不满足的 减去3个不满足的 …… 之后就是组合数的计算,对于一个非常大的模数,我们可以将它唯一分解,之后CRT还原即可 但是我们有可能不存在逆元,数据范围不允许我们递推计算组合数 我们知道没有逆元当且仅当(a,p)不互素,我们可以将阶乘分成两部分:互素和不

组合数取模终极版

以前讲述过很多组合数取模问题,详见:http://blog.csdn.net/acdreamers/article/details/8037918 今天,我们继续学习一些稍有难度的组合数取模问题,比如大组合数对合数取模,求大组合数的最后位数字等等. 首先来看组合数对合数取模问题 问题:求的值,其中和,并且是合数. 分析:先把素因子分解,然后转化为求,这里为素数,然后用CRT合并.所以现在重点来研究 如何求的值.这个问题AekdyCoin大神已经详细讲述了,如下链接     链接:http://h

排列组合+组合数取模 HDU 5894

1 // 排列组合+组合数取模 HDU 5894 2 // 题意:n个座位不同,m个人去坐(人是一样的),每个人之间至少相隔k个座位问方案数 3 // 思路: 4 // 定好m个人 相邻人之间k个座位 剩下就剩n-(m+1)*k个座位 5 // 剩下座位去插m个不同的盒子==就等价n个相同的球放m个不同的盒子 6 // 然后组合数出来了 7 // 乘n的话是枚举座位,除m是去掉枚举第一个座位的时候,剩下人相邻的座位相对不变的情况 8 9 #include <iostream> 10 #incl

大组合数取模之lucas定理模板,1&lt;=n&lt;=m&lt;=1e9,1&lt;p&lt;=1e6,p必须为素数

typedef long long ll; /********************************** 大组合数取模之lucas定理模板,1<=n<=m<=1e9,1<p<=1e6,p必须为素数 输入:C(n,m)%p 调用lucas(n,m,p) 复杂度:min(m,p)*log(m) ***********************************/ //ax + by = gcd(a,b) //传入固定值a,b.放回 d=gcd(a,b), x , y

组合数取模Lucas定理及快速幂取模

组合数取模就是求的值,根据,和的取值范围不同,采取的方法也不一样. 下面,我们来看常见的两种取值情况(m.n在64位整数型范围内) (1)  , 此时较简单,在O(n2)可承受的情况下组合数的计算可以直接用杨辉三角递推,边做加法边取模. (2) ,   ,并且是素数 本文针对该取值范围较大又不太大的情况(2)进行讨论. 这个问题可以使用Lucas定理,定理描述: 其中 这样将组合数的求解分解为小问题的乘积,下面考虑计算C(ni, mi) %p. 已知C(n, m) mod p = n!/(m!(

hdu 3037 Saving Beans 组合数取模模板题。。

Saving Beans Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2707    Accepted Submission(s): 1014 Problem Description Although winter is far away, squirrels have to work day and night to save b

toj 4111 组合数取模 暴力分解

题目大意:组合数取模,n和m并不算大,p比较大且是合数. 思路:暴力分解+快速幂 注:暴力也是有区别的,分解质因数时可以用以下work函数,写的非常巧妙,摘录自互联网. 1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 5 typedef long long ll; 6 const ll mod = 1ll << 32; 7 const int N = 1000001; 8 const