组合数学
组合数学
基本概念
1. 加法原理和乘法原理
- 加法原理:一件事情可以用 \(m\) 类不同的方法完成,其中第 \(i\) 类方法有 \(a_i\) 中不同的方法。则总方法数为 \(\sum\limits_{i=1}^{m}a_i\)
- 乘法原理:假设两个集合 \(S_A\) 与 \(S_B\),\(S_A\) 中每一个选择都对应了 \(S_B\) 中的 \(|S_B|\) 个数,则总方案数为 \(|S_A|·|S_B|\)。推广得到:有 \(m\) 个集合 \(S_i\),每个集合选一个数,则总方案数为 \(\prod\limits_{i=1}^{m}|S_i|\)
2. 排列
排列是有序的,理解为把 \(n\) 个元素的集合 \(S\) 的一个 \(m\) 排列理解为在这 \(n\) 个元素中选 \(m\) 个进行有序摆放的总方案数
-
不可重复排列数:从 \(n\) 个不同的物体中不重复地取出 \(m\) 个,记作 \(P_n^m = \cfrac{n!}{(n-r)!} = n(n-1)(n-2)\cdots(n-m+1)\)
-
可重复排列数:从 \(n\) 个不同的物体中可重复地取出 \(m\) 个,排列数为 \(n^r\)
-
循环排列:从 \(n\) 个构成环的不同的物体中可重复地取出 \(m\) 个,排列数为 \(\cfrac{P_n^m}{m}\)
3. 组合
排列是有序的,组合是无序的,理解为把 \(n\) 个元素的集合 \(S\) 的一个 \(m\) 排列理解为在这 \(n\) 个元素中选 \(m\) 个进行无序摆放(相同元素构 成的不同方案算作一个)的总方案数,记作\(C_n^m\)
当所有元素互不相同时,组合数 \(C_n^m=\cfrac{P_n^m}{r!}=\cfrac{n!}{r!(n-r)!}=\cfrac{n(n-1)\cdots(n-m+1)}{r(r-1)\cdots 2\times1}\)
组合数有三个重要性质:
-
\(C_n^m=C_n^{n-m}\):从 \(n\) 个元素中选 \(m\) 个等价于在 \(n\) 个元素中选择丢弃 \(n-m\) 个
-
\(C_n^{m}=C_{n-1}^{m}+C_{n-1}^{m-1}\)(帕斯卡公式):可以如下证明:
- 选第 \(n\) 个元素 \(\rightarrow\) 在剩下的 \(n-1\) 个元素中还要选 \(m-1\)个(\(C_{n-1}^{m-1}\))
- 不选第 \(n\) 个元素 \(\rightarrow\) 在剩下的 \(n-1\) 个元素中还要选 \(m\) 个(\(C_{n-1}^{m}\))
通过加法原理可以得出 \(C_n^{m}=C_{n-1}^{m}+C_{n-1}^{m-1}\)
-
\(\sum\limits_{i=0}^{n}C_n^i=2^n\):\(C_n ^m\)在二进制中的表示为在所有长度为 \(n\) 的二进制 \(01\) 串,其中有 \(m\) 个 \(1\)的串有几个。而长度取 \([0,n]\) 时总方案为 \([0,2^n]\) 的二进制数的数量
二项式定理与杨辉三角
观察 \((x+y)^n\) 的展开项:
其左侧的展开式 \((x+y)^n\) 中系数构成杨辉三角第 \(n+1\) 行
我们需要逐个通过杨辉三角计算 \((x+y)^n\) 的各项系数,这样复杂度会较高。我们用二项式定理快速求解。
我们将 \((x+y)^n\) 展开为 \((x+y)(x+y)\cdots(x+y)\),我们将这些 \((x+y)\) 看作装着两个元素 \(x\) 和 \(y\) 的集合。而在展开式中第 \(m\) 项目(第 \(1\) 个算作第 \(0\) 项)为 \(k·x^{n-m}y^{m}\),可以理解为在这 \(n\) 个集合中选 \(n-m\) 个 \(x\)(选 \(m\) 个 \(y\))相乘,有 \(C_n^{m}\) 种方案,也就是系数为\(C_n^{m}\)
推导得:
而计算二项式系数有两种方法:
-
递推法:\(C_n^{m}=C_{n-1}^{m}+C_{n-1}^{m-1}\),这个递推式就是杨辉三角的定义,数值等于“两肩”和(时间复杂度为 \(O(n^2)\))
for (int i = 1;i <= n;i ++) c[i][1] = c[i][i] = 1; for (int j = 2;j < i;j ++) c[i][j] = c[i - 1][j] + c[i - 1][j - 1]; -
用公式直接算: \(C_n^m=\cfrac{n!}{r!(n-r)!}\)
由于二项式系数增长极快,大部分情况都需要取模,需要用到逆
\(C_n^m~mod~p=\cfrac{n!}{r!(n-r)!}~mod~p=(n! ~ mod ~ p)(r!)^{-1}((n-r)!)^{-1}~~(mod~p)\)
时间复杂度为 \(O(n)\)
void init(int n) { fac[0] = 1ll; for (int i = 1;i <= n;i ++) { fac[i] = (fac[i - 1] * i) % mod; // 预处理 i! inv[i] = fastPow(fac[i],mod - 2ll); // 费马小定理求i!的逆 } } ll C(int n,int m) { return (fac[n] * inv[m] % mod * inv[n - m] % mod) % mod; }
卢卡斯定理
卢卡斯定理用于计算组合数取模,即 \(C_n ^m~mod~p\),\(m\) 为质数
组合数的计算可以用逆,但这种计算方法也有局限性。通过逆的定义( \(ax\equiv 1~(mod~p)\) 的一个解)我们可以得出 \(p\) 最好是大于 \(a\) 的质数,才能保证 \(\gcd(a,p)=1\)。在组合数的计算 \(C_n^m=(n! ~ mod ~ p)(r!)^{-1}((n-r)!)^{-1}~~(mod~p)\) 中,如果 \(p<n\),就无法保证 \((n!)^{-1}\) 和 \(((n-r)!)^{-1}\) 的逆的存在。而卢卡斯定理则可以解决这类问题
卢卡斯定理 对于非负整数 \(n\),\(m\) 和素数 \(p\),有:
其中 \(n=n_kp^k+\cdots+n_1p+n_0\) 和 \(m=m_kp^k+\cdots+m_1p+m_0\) 是 \(n\) 与 \(m\) 的 \(p\) 进制展开式
意义是对 \(p\) 进制下的 \(n\) 和 \(m\) 的每一位计算组合数,最后乘起来
但在编程中我们通常使用另外一个表达:
在计算时我们将两边拆开
- 左侧 \(C_{n~mod~p}^{m~mod~p}\) 用逆直接算(\((n~mod~p)<p\))
- 右侧 \(C_{n/p}^{m/p}\) 则递归继续计算(\(m=0\) 时结束递归并返回 \(C_n^0=1\))
ll Lucas(ll n,ll m,ll p) {
if (p == 0) return 1ll;
return C(n % p,m % p,p) * Lucas(n / p,m / p,p) % p;
}

浙公网安备 33010602011771号