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

浙公网安备 33010602011771号