求组合数的三种算法

组合数

一、预处理组合数

核心

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=Ca−1b+Ca−1b−1

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

Cnk = { 1 , k = = 0 Cn - 1k + Cn - 1k - 1 , 0 < k ≤ n a n d n ≥ 1 \begin{split} \text{Cnk}=\begin{cases} 1&,k==0\\ \text{Cn - 1k + Cn - 1k - 1}&,0<k\leq n~and~n\geq1 \end{cases} \end{split} Cnk={1Cn - 1k + Cn - 1k - 1,k==0,0<k≤n and n≥1

cpp 复制代码
#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; 
}

预处理后,直接访问 Cnk \text{Cnk} Cnk,就能得到 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!(a−b)!a!=a!∗(b!)−1∗((a−b)!)−1

适用范围 : a a a 较大的情况下,如 a ≤ 1 0 5 a\leq 10^{5} a≤105。
算法简析

先来看逆元费马小定理的定义:

  • 1、逆元 :对于模 m m m,有两个数 a a a 和 b b b,若 a b ≡ 1 ( mod m ) ab\equiv1(\text{mod}~m) ab≡1(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=a−1, a=b−1。
  • 2、费马小定理 :若 p p p 是一个素数,且 a a a 不被 p p p 整除,则 a p − 1 ≡ 1 ( mod p ) a^{p-1}\equiv1(\text{mod}~p) ap−1≡1(mod p),即 a p − 1 a^{p-1} ap−1 除以 p p p 的余数为1。
    由模运算的性质, ≡ \equiv ≡ 两边同乘 a a a 的逆元 a − 1 a^{-1} a−1,得 a p − 2 ≡ a − 1 ( mod p ) a^{p-2}\equiv a^{-1}(\text{mod}~p) ap−2≡a−1(mod p)。在模 p p p 的意义下, a p − 2 a^{p-2} ap−2 和 a − 1 a^{-1} a−1 等价。求 a − 1 a^{-1} a−1,就转换为求 a p − 2 a^{p-2} ap−2。

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

  • 1、阶乘:由

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

得,

facta = facta - 1 * a \text{facta = facta - 1 * a} facta = facta - 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)p−2(a!)−1≡(a)−1(mod p)=((a−1)!)−1∗a−1

得,

infacta = infacta - 1 ∗ a − 1 a − 1 = pow(a, p - 2) mod p \begin{split} \text{infacta}&=\text{infacta - 1} \ast a^{-1} \\ a^{-1}&=\text{pow(a, p - 2) mod p} \end{split} infactaa−1=infacta - 1∗a−1=pow(a, p - 2) mod p

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

cpp 复制代码
#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 = factn * infactk * infactn - k C_n^k=\text{factn * infactk * infactn - k} Cnk=factn * infactk * infactn - 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) Cab≡Ca mod pb mod p ⋅ C⌊a/p⌋⌊b/p⌋(mod p)

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

cpp 复制代码
#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;
}

相关推荐
随意起个昵称7 分钟前
贪心模型-Johnson法则
c++·算法
旋生万物30 分钟前
电磁力 = 螺旋联络的 90° 旋转?麦克斯韦方程组的几何重构
人工智能·python·算法·机器学习·copilot·世界模型·物理ai
余俊晖1 小时前
多模态大模型细粒度视觉理解:Vision-OPD在线策略自蒸馏技术方案概述
人工智能·深度学习·算法·多模态·opd
脚踏实地皮皮晨1 小时前
003003001_Grid控件
开发语言·windows·算法·c#·visual studio
Hi李耶1 小时前
【LeetCode】6-Z字形变换
算法·leetcode·职场和发展
XiaoYu1__1 小时前
算法进阶·其一:用SCC、Tarjan算法与缩点思想解决有向图的连通关系及2-SAT问题的拓展
算法
小O的算法实验室2 小时前
AAAI-26,全局相关性学习:通过谱图神经网络增强进化算法
神经网络·学习·算法
段一凡-华北理工大学2 小时前
AI推动工业智能化转型~系列文章07:分类与诊断算法体系:故障识别的完整工具箱
数据库·人工智能·算法·机器学习·分类·数据挖掘·高炉炼铁智能化
简信CRM2 小时前
适合离散制造中小工厂 MES 系统推荐
算法·机器学习·制造
AKA__Zas2 小时前
芝士算法(前缀和2.0)
java·数据结构·算法·leetcode·哈希算法·学习方法