Lucas 定理
💡 核心思想
Lucas 定理用于计算大组合数 C(n,m) mod p,其中 p 为质数。定理内容:C(n,m) ≡ C(n mod p, m mod p) · C(n/p, m/p) (mod p)。它将大组合数递归分解为多个小组合数的乘积,每个小组合数可以用预处理的阶乘和逆元在 O(1) 时间内计算。
Lucas 定理是处理大组合数取模的利器。当 n 和 m 很大(10^18 级别)而模数 p 是质数且不太大(10^6 级别)时,Lucas 定理就是标准解法。很多选手看到大组合数就放弃,其实记住 Lucas 的递归公式,配合预处理的阶乘逆元,代码非常短。
🎯 直觉理解
Lucas 定理处理"模质数 p 固定、但 n 和 m 很大"的组合数 $C(n,m)mod p$:直接算阶乘根本放不下。
定理内容:把 n、m 写成 p 进制,则 $C(n,m)$ 模 p 等于每个"位"分别算组合数再相乘——大问题拆成 p 进制各位的小问题,每一位的数字都小于 p,可以用小阶乘 + 逆元直接算。
适用前提:p 必须是小质数(阶乘表能开下,一般 $10^5$ 左右);p 不质数时得用扩展 Lucas。先验证一个具体例子(比如 p=7 的小数据)理解拆分过程,再背模板。
📝 算法流程
- 预处理 0 到 p-1 的阶乘 fac[] 和逆元 inv[]
- Lucas(n, m, p):
- 如果 m == 0,返回 1
- 返回 C(n%p, m%p, p) * Lucas(n/p, m/p, p) % p
- 其中 C(n,m,p) = fac[n] * inv[m] % p * inv[n-m] % p
$$\binom{n}{m} \equiv \binom{n \bmod p}{m \bmod p} \cdot \binom{\lfloor n/p \rfloor}{\lfloor m/p \rfloor} \pmod{p}$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | $O(p + \log_p n)$ 预处理 + 查询 |
| 空间 | $O(p)$ |
💻 参考实现(C++)
C++ (C++17)
#include
using namespace std;
using ll = long long;
const int P = 1000007; // 质数模数
ll fac[P], inv[P];
ll qpow(ll a, ll n, ll mod) {
ll res = 1; a %= mod;
while (n) { if (n & 1) res = res * a % mod; a = a * a % mod; n >>= 1; }
return res;
}
void init() {
fac[0] = 1;
for (int i = 1; i < P; i++) fac[i] = fac[i-1] * i % P;
inv[P-1] = qpow(fac[P-1], P-2, P);
for (int i = P-2; i >= 0; i--) inv[i] = inv[i+1] * (i+1) % P;
}
ll C(ll n, ll m) {
if (m > n || m < 0) return 0;
return fac[n] * inv[m] % P * inv[n-m] % P;
}
ll lucas(ll n, ll m) {
if (m == 0) return 1;
return C(n % P, m % P) * lucas(n / P, m / P) % P;
}
int main() {
init();
cout << lucas(1000, 300) << endl;
return 0;
} ⚠️ 常见坑点
模数必须是质数——Lucas 定理要求 p 为质数
m > n 的情况——返回 0
阶乘预处理到 p-1——不是到 n
递归深度——log_p(n) 层,通常不会栈溢出
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P3807 模板 Lucas 定理 | 洛谷 | CSP-S | Lucas 定理模板题 |
| P1313 计算系数 | 洛谷 | CSP-S | Lucas 定理应用 |
| P2820 组合数问题 | 洛谷 | CSP-S | Lucas + 前缀和 |