组合数

一、预处理组合数

核心

\[C_a^b = C_{a-1}^b + C_{a-1}^{b-1} \]

适用范围\(a\) 较小的情况下,如 \(a \leq 10^3\)
算法简析:令 \(\text{C[n][k]}=C_n^k\),规定 \(\text{C[0][0] = 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} \]

#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; 
}

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


二、预处理阶乘

核心

\[C_a^b=\frac{a!}{b!(a-b)!}=a!\ast (b!)^{-1}\ast ((a-b)!)^{-1} \]

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

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

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

  • 1、阶乘:由

\[a!=(a-1)!\ast a \]

得,

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

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

\[\begin{split} (a)^{p-2}&\equiv (a)^{-1}(\text{mod}~p) \\ (a!)^{-1}&=((a-1)!)^{-1}\ast a^{-1} \end{split} \]

得,

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

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

#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=\text{fact[n] * infact[k] * infact[n - k]}\)。注意:计算过程中可能会溢出,要进行模运算。


三、卢卡斯定理

核心:卢卡斯定理

\[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) \]

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

#define MOD 6662333

int fact[MOD + 3], 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 < MOD; 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;
}

四、组合数的一些性质

  • \(\sum_i{C_n^i}=2^{n-1},~~~~i~是偶数或奇数\)

posted @   ltign  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示