古典叙事 · 技术札记

序章

山河有卷
人间有声

写代码,也写长风、旧城与未熄的灯。
愿每一篇随笔,都有自己的山水与回声。
阅览随笔

Lucas定理

Lucas定理

Lucas 定理的数学公式长这样:$$C_N^M \equiv C_{N \bmod p}^{M \bmod p} \times C_{\lfloor N/p \rfloor}^{\lfloor M/p \rfloor} \pmod p$$你看这个公式,\(\bmod p\) 不就是取最后一位数吗?\(\lfloor /p \rfloor\) 不就是把最后一位数砍掉,把剩下的数往右移一位吗?假设 \(p=5\)。我们求 \(C_{27}^{11} \pmod 5\):把 27 变成 5 进制:\(27 = (102)_5\) (即 \(1 \times 25 + 0 \times 5 + 2 \times 1\))把 11 变成 5 进制:\(11 = (021)_5\)Lucas 定理告诉我们,直接把每一位对齐算组合数相乘即可:$$C_{27}^{11} \equiv C_1^0 \times C_0^2 \times C_2^1 \pmod 5$$一旦对应位上出现了“底数小于顶数”的情况(比如上面的 \(C_0^2\)),这个位的值就是 0,整个式子的结果直接就是 0。

Lucas定理常见用法

可以把组合数优化成多个组合数相乘,便于计算,但要求p是一个质数,如果p不是质数,可以把p拆成质因子,然后用多个lucas定理算出对应的值,然后用中国剩余定理算出真正的值

Lucas定理模板代码

#include <iostream>
using namespace std;

typedef long long ll;
const int MAXP = 100005; // p 的最大值

ll fact[MAXP], inv[MAXP];

// 1. 快速幂求逆元
ll qpow(ll base, ll exp, ll mod) {
    ll res = 1;
    while (exp > 0) {
        if (exp & 1) res = res * base % mod;
        base = base * base % mod;
        exp >>= 1;
    }
    return res;
}

// 2. 预处理阶乘和逆元 (在 p 的范围内)
// 因为每组数据的 p 不同,所以每次查询前都要重新初始化
void init(ll p) {
    fact[0] = 1;
    inv[0] = 1;
    for (int i = 1; i <= p - 1; i++) {
        fact[i] = fact[i - 1] * i % p;
    }
    // 费马小定理求 p-1 阶乘的逆元
    inv[p - 1] = qpow(fact[p - 1], p - 2, p);
    // 倒推求所有逆元
    for (int i = p - 2; i >= 1; i--) {
        inv[i] = inv[i + 1] * (i + 1) % p;
    }
}

// 3. 计算基础组合数 (a, b < p)
ll C(ll a, ll b, ll p) {
    if (b > a) return 0; // 只要有一位出现 b > a,直接返回 0
    if (b == 0) return 1;
    return fact[a] * inv[b] % p * inv[a - b] % p;
}

// 4. Lucas 定理:一直除再取余数的完美体现
ll lucas(ll a, ll b, ll p) {
    ll ans = 1;
    while (a > 0 && b > 0) {
        // 取出当前 p 进制下的最后一位
        ll a_mod = a % p;
        ll b_mod = b % p;
        
        // 计算这一位的组合数并乘进答案
        ans = ans * C(a_mod, b_mod, p) % p;
        
        // 如果乘的过程中变成了 0,提前结束
        if (ans == 0) break; 
        
        // 砍掉最后一位,处理下一位
        a /= p;
        b /= p;
    }
    return ans;
}

void solve() {
    ll n, m, p;
    cin >> n >> m >> p;
    
    // 题目要求 C(n+m, n) mod p
    ll N = n + m;
    ll M = n; 
    
    // 预处理 0 到 p-1 的阶乘和逆元
    init(p);
    
    cout << lucas(N, M, p) << "\n";
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    int T;
    cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}
posted @ 2026-07-28 13:37  Morphis‘  阅读(11)  评论(0)    收藏  举报