求组合数的三种算法

文章介绍了三种计算组合数的方法:预处理组合数的递推公式、利用阶乘和逆元的预处理以及卢卡斯定理。着重讨论了在不同规模(a≤10^3和a≤10^5)下的计算策略,以及如何通过快速幂和模运算优化算法效率。
摘要由CSDN通过智能技术生成

一、预处理组合数

核心

C a b = C a − 1 b + C a − 1 b − 1 C_a^b = C_{a-1}^b + C_{a-1}^{b-1} Cab=Ca1b+Ca1b1

适用范围 a a a 较小的情况下,如 a ≤ 1 0 3 a \leq 10^3 a103
算法简析:令 C[n][k] = C n k \text{C[n][k]}=C_n^k C[n][k]=Cnk,规定 C[0][0] = 1 \text{C[0][0] = 1} C[0][0] = 1,则

C[n][k] = { 1 , k = = 0 C[n - 1][k] + C[n - 1][k - 1] , 0 < k ≤ n   a n d   n ≥ 1 \begin{split} \text{C[n][k]}=\begin{cases} 1&,k==0\\ \text{C[n - 1][k] + C[n - 1][k - 1]}&,0<k\leq n~and~n\geq1 \end{cases} \end{split} C[n][k]={1C[n - 1][k] + C[n - 1][k - 1],k==0,0<kn and n1

#define MAX 4000
#define MOD 6662333

int C[MAX][MAX], n;

void solve(void)
{
	for (int i = 0; i <= n; i++)
		for (int j = 0; j <= i; j++)
			if (j == 0)    C[i][j] = 1;
			else
				C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD; 
}

预处理后,直接访问 C[n][k] \text{C[n][k]} C[n][k],就能得到 C n k C_n^k Cnk 的值。


二、预处理阶乘

核心

C a b = a ! b ! ( a − b ) ! = a ! ∗ ( b ! ) − 1 ∗ ( ( a − b ) ! ) − 1 C_a^b=\frac{a!}{b!(a-b)!}=a!\ast (b!)^{-1}\ast ((a-b)!)^{-1} Cab=b!(ab)!a!=a!(b!)1((ab)!)1

适用范围 a a a 较大的情况下,如 a ≤ 1 0 5 a\leq 10^{5} a105
算法简析
先来看逆元费马小定理的定义:

  • 1、逆元:对于模 m m m,有两个数 a a a b b b,若 a b ≡ 1 ( mod  m ) ab\equiv1(\text{mod}~m) ab1(mod m),即 a a a b b b 的乘积除以 m m m 的余数为1,则 a a a b b b 互为模 m m m 意义下的乘法逆元。记 b = a − 1 ,   a = b − 1 b=a^{-1},~a=b^{-1} b=a1, a=b1
  • 2、费马小定理:若 p p p 是一个素数,且 a a a 不被 p p p 整除,则 a p − 1 ≡ 1 ( mod  p ) a^{p-1}\equiv1(\text{mod}~p) ap11(mod p),即 a p − 1 a^{p-1} ap1 除以 p p p 的余数为1。
    由模运算的性质, ≡ \equiv 两边同乘 a a a 的逆元 a − 1 a^{-1} a1,得 a p − 2 ≡ a − 1 ( mod  p ) a^{p-2}\equiv a^{-1}(\text{mod}~p) ap2a1(mod p)。在模 p p p 的意义下, a p − 2 a^{p-2} ap2 a − 1 a^{-1} a1 等价。求 a − 1 a^{-1} a1,就转换为求 a p − 2 a^{p-2} ap2

现在,我们的重点是求阶乘阶乘的逆元。我们用两个数组 fact[]infact[] 分别表示阶乘(fact[a] a ! a! a!)和阶乘的逆元(infact[a] 表示 ( a ! ) − 1 (a!)^{-1} (a!)1)。规定 fact[0] = infact[0] = 1 \text{fact[0] = infact[0] = 1} fact[0] = infact[0] = 1

  • 1、阶乘:由

a ! = ( a − 1 ) ! ∗ a a!=(a-1)!\ast a a!=(a1)!a

得,

fact[a] = fact[a - 1] * a \text{fact[a] = fact[a - 1] * a} fact[a] = fact[a - 1] * a

  • 2、阶乘的逆元:由费马小定理,在模 p p p 下,

( a ) p − 2 ≡ ( a ) − 1 ( mod  p ) ( a ! ) − 1 = ( ( a − 1 ) ! ) − 1 ∗ a − 1 \begin{split} (a)^{p-2}&\equiv (a)^{-1}(\text{mod}~p) \\ (a!)^{-1}&=((a-1)!)^{-1}\ast a^{-1} \end{split} (a)p2(a!)1(a)1(mod p)=((a1)!)1a1

得,

infact[a] = infact[a - 1] ∗ a − 1 a − 1 = pow(a, p - 2) mod p \begin{split} \text{infact[a]}&=\text{infact[a - 1]} \ast a^{-1} \\ a^{-1}&=\text{pow(a, p - 2) mod p} \end{split} infact[a]a1=infact[a - 1]a1=pow(a, p - 2) mod p

注:计算逆元时,可以通过快速幂来提高算法效率。

#define MAX 4000
#define MOD 6662333

int fact[MAX], infact[MAX], n;

typedef long long ll;

int qum(int x, int n, int mod)
{
	ll ret = 1;
	while (n > 0)
	{
		if (n & 1)    ret = ret * x % MOD;
		x = (ll)x * x % MOD;
		n >>= 1;
	}
	return ret;	
} 

void solve(void)
{
	fact[0] = infact[0] = 1;
	for (int i = 1; i <= n; i++)
	{
		fact[i] = (ll)fact[i - 1] * i % MOD;
		infact[i] = (ll)infact[i - 1] * qum(i, MOD - 2, MOD) % MOD;
	}
}

预处理后, C n k = fact[n] * infact[k] * infact[n - k] C_n^k=\text{fact[n] * infact[k] * infact[n - k]} Cnk=fact[n] * infact[k] * infact[n - k]。注意:计算过程中可能会溢出,要进行模运算。


三、卢卡斯定理

核心:卢卡斯定理

C a b ≡ C a  mod  p b  mod  p   ⋅   C ⌊ a / p ⌋ ⌊ b / p ⌋ ( mod  p ) C_a^b\equiv C_{a~\text{mod}~p}^{b~\text{mod}~p}~·~C_{\lfloor a/p \rfloor}^{\lfloor b/p \rfloor}(\text{mod}~p) CabCa mod pb mod p  Ca/pb/p(mod p)

适用范围 a a a 很大的情况,比如 a ≤ 1 0 18 a \leq 10^{18} a1018
算法简析:若 a a a b b b 很大,我们可以通过卢卡斯定理缩小 a a a b b b,直至 a ,   b < p a,~b< p a, b<p ( p p p 一般是较小的素数)。这时,在使用前两种方法求解。

#define MAX 4000
#define MOD 6662333

int fact[MAX], n;

typedef long long ll;

int qum(int x, int n, int mod)
{
	ll ret = 1;
	while (n > 0)
	{
		if (n & 1)    ret = ret * x % MOD;
		x = (ll)x * x % MOD;
		n >>= 1;
	}
	return ret;	
} 

void init(void)
{
	fact[0] = 1;
	for (int i = 1; i <= n; i++)
	{
		fact[i] = (ll)fact[i - 1] * i % MOD;
	}
}

int C(int a, int b, int mod)
{
	if (a < b)    return 0;
	return (ll)fact[a] * qum(fact[b], mod - 2, mod) % mod * qum(fact[a - b], mod - 2, mod) % mod;
}

int lucas(int a, int b, int mod)
{
	if (a < mod && b < mod)    return C(a, b, mod);
	return (ll)C(a % mod, b % mod, mod) * lucas(a / mod, b / mod, mod) % mod;
}

第一章 引论 1.1 组合数学研究的对象 1.2 组合问题典型实例 1.2.1 分派问题 1. 2.2 染色问题 1.2.3 幻方问题 1.2.4 36军官问题 1.2.5 中国邮路问题 习 题 第二章 排列与组合 2.1 两个基本计数原理 2.2 无重集的排列与组合 2.3 重集的排列与组合 2.4 排列生成算法 2.4.1 序数法 2.4.2 字典序法 2.4.3 轮转法 2.5 组合生成算法 .2.6 应用举例 习 题 第三章 容斥原理 3.1 引 言 3.2 容斥原理 3.3 几个重要公式 3.4 错位排列 3.5 有限制的排列 3.6 棋阵多项式 3.7 禁位排列 习 题 第四章 鸽巢原理 4.1 鸽巢原理 4. 2 鸽巢原理的推广形式 4. 3 ramsey数 4.4 ramsey数的性质 4.5 ramsey定理 习 题 第五章 母函数 5.1 母函数概念 5.2 幂级数型母函数 5.3 整数的拆分 5.4 ferrers图 5.5 指数型母函数 习 题 第六章 递归关系 6.1 引言 6.2 几个典型的递归关系.. 6.3 用母函数方法解递归关系 6.4 常系数线性齐次递归关系的解 6.5 常系数线性非齐次递归关系的解 6.6 非常系数非线性递归关系的解 6.7 差分表法 6.8 stirling数 习 题 第七章 polya定理 7.1 有限集的映射 7.2 群的基本概念 7.3 置换群 7.4 置换的奇偶性 7.5 置换群下的共轭类 7.6 burnside引理 7.7 polya定理 7.8 polya定理的母函数型式 7.9 不标号图的计数 习 题 第八章 图论基础 8.1 图的基本概念 8.2 同构图、完全图与二分图 8.3 通路、回路与图的连通性 8.4 euler图与hamilton图 8.5 割集与树 8.6 图的矩阵表示法 8.7 平面图、对偶图与色数 8.8 匹配理论 8.9 网络流 习 题 第九章 拉丁方与区组设计 9.1 引言 9.2 拉丁方 9.3 有限域 9.4 正交拉丁方的构造 9.5 完全区组设计 9.6 平衡不完全区组设计(bibd) 9.7 区组设计的构造 9.8 steiner三连系 9.9 hadamard矩阵 习 题 第十章 线性规划 10.1 lp问题引例 10.2 lp问题的一般形式 10.3 lp问题的标准型 10.4 可行域和最优可行解 10.5 单纯形法 10.6 单纯形表格法 10.7 两阶段法 10.8 对偶原理 10.9 对偶单纯形法 10.10 应用举例 习 题 第十一章 组合优化算法与计算的时间复杂度理论 11.1 dijkstra算法 11.2 floyd算法 11.3 kruskal算法 11.4 最优树的破圈法和统观法 11.5 二分图中最大匹配与最佳匹配的算法 11.6 fleury算法 11.7 中国邮路问题及其算法 11.8 深度优先搜索法--dfs算法 11.9 项目网络与关键路径法 11.10 网络最大流算法 11.11 状态转移法 11.12 好算法、坏算法和np类问题 11.13 npc类问题 11.14 货郎问题的近似解 习 题... 参考文献
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值