ZT Blog
algorithm

卢卡斯定理

卢卡斯定理(Lucas' Theorem)是组合数学中用于计算大组合数模素数的核心定理,其核心思想是将大组合数分解为小组合数的乘积。以下结合参考文献进行详细讲解:

定理内容

对于素数 pp 和非负整数 n,kn, k,有:

(nk)(n/pk/p)(nmodpkmodp)(modp)\binom{n}{k} \equiv \binom{\lfloor n/p \rfloor}{\lfloor k/p \rfloor} \cdot \binom{n \bmod p}{k \bmod p} \pmod{p}

其中:

  • \lfloor \cdot \rfloor 表示向下取整;
  • nmodpn \bmod pkmodpk \bmod p 是模 pp 的余数(范围在 [0,p1][0, p-1])。

证明思路

证明基于二项式展开和模运算性质:

  1. 二项式展开: 考虑 (1+x)n(1+x)pn/p(1+x)nmodp(modp)(1+x)^n \equiv (1+x)^{p \lfloor n/p \rfloor} \cdot (1+x)^{n \bmod p} \pmod{p}。 由于 (1+x)p1+xp(modp)(1+x)^p \equiv 1+x^p \pmod{p}(费马小定理),右侧可化为 (1+xp)n/p(1+x)nmodp(1+x^p)^{\lfloor n/p \rfloor} \cdot (1+x)^{n \bmod p}

  2. 系数匹配: 左侧 xkx^k 的系数为 (nk)\binom{n}{k},右侧需将 kk 分解为 k=pk/p+(kmodp)k = p \lfloor k/p \rfloor + (k \bmod p)。 此时 xkx^k 的系数为:

    (n/pk/p)(nmodpkmodp)\binom{\lfloor n/p \rfloor}{\lfloor k/p \rfloor} \cdot \binom{n \bmod p}{k \bmod p}

    等式成立。

  3. 边界处理

    • n/p<k/p\lfloor n/p \rfloor < \lfloor k/p \rfloor,则 (nk)0(modp)\binom{n}{k} \equiv 0 \pmod{p}
    • kmodp>nmodpk \bmod p > n \bmod p,则 (nmodpkmodp)=0\binom{n \bmod p}{k \bmod p} = 0

算法实现(C++ 代码)

#include <iostream>
using namespace std;

// 预处理阶乘及其逆元(用于小组合数计算)
const int MAX_P = 10000; // 假设 p < MAX_P
long long fact[MAX_P], inv_fact[MAX_P];

// 快速幂求逆元
long long qpow(long long a, long long b, long long p) {
    long long res = 1;
    while (b) {
        if (b & 1) res = res * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return res;
}

// 初始化阶乘表
void init(long long p) {
    fact = 1;
    for (int i = 1; i < p; i++) 
        fact[i] = fact[i - 1] * i % p;
    inv_fact[p - 1] = qpow(fact[p - 1], p - 2, p); // 费马小定理求逆元
    for (int i = p - 2; i >= 0; i--)
        inv_fact[i] = inv_fact[i + 1] * (i + 1) % p;
}

// 计算小组合数 C(n, k) mod p(要求 n, k < p)
long long C_small(long long n, long long k, long long p) {
    if (k < 0 || k > n) return 0;
    return fact[n] * inv_fact[k] % p * inv_fact[n - k] % p;
}

// 卢卡斯定理递归计算
long long Lucas(long long n, long long k, long long p) {
    if (k == 0) return 1; // 递归终止条件
    long long n1 = n / p, k1 = k / p;        // 整除部分
    long long n2 = n % p, k2 = k % p;        // 余数部分
    if (k2 > n2) return 0;                   // 边界:余数组合数为0
    return C_small(n2, k2, p) * Lucas(n1, k1, p) % p;
}

int main() {
    long long n, k, p;
    cin >> n >> k >> p;
    init(p); // 预处理阶乘
    cout << Lucas(n, k, p) << endl;
    return 0;
}

关键点解析

  1. 递归分解: 将大组合数 (nk)\binom{n}{k} 递归分解为 (n/pk/p)\binom{\lfloor n/p \rfloor}{\lfloor k/p \rfloor}(nmodpkmodp)\binom{n \bmod p}{k \bmod p},直到 n,k<pn, k < p

  2. 预处理优化

    • 预处理阶乘表 fact[] 及其逆元 inv_fact[],使小组合数计算 O(1)O(1)
    • 利用费马小定理求逆元(要求 pp 是素数)。
  3. 时间复杂度

    • 递归深度 O(logpn)O(\log_p n),每层计算 O(1)O(1)
    • 预处理 O(p)O(p),总复杂度 O(p+logpn)O(p + \log_p n),适用于 pp 较小的情况。
  4. 与 exLucas 的关系: 当模数非素数时需用 exLucas 算法,但模数为素数时两者结果等价。


注意事项

  • 适用条件:仅当模数 pp 为素数时成立;
  • 边界处理:递归中需检查 kmodpnmodpk \bmod p \leq n \bmod p,否则组合数为 0。

此实现满足 OI/ACM 风格,兼顾效率与可读性。

卢卡斯定理 - ZT Blog