Lucas 定理及其实现
本文不涉及定理的证明(作者太菜了不会)。
公式
Lucas 定理可以在模数是质数且不大的情况下求解大组合数取模问题:
对于素数 \(p\),有
\[(^{n}_{k}) \equiv (^{\lfloor {n \over p} \rfloor}_{\lfloor {k \over p} \rfloor})(^{n \mod p}_{k \mod p}) (mod p) \]当 \(n < k\) 时,\((^{n}_{k})\) 规定为 0.
实现
由于模数不大,所以对于公式的后一部分 \((^{n \mod p}_{k \mod p})\) 我们可以通过预处理阶乘与阶乘逆元根据定义快速计算,前一部分 \((^{\lfloor {n \over p} \rfloor}_{\lfloor {k \over p} \rfloor})\) 就递归着去算:
int C(int n, int m) {
i64 res = 1;
// 由于 m <= n
// 所以 m 变成 0 后组合数的结果就都是 1 了
while (m) {
// cnt(n, m) 是根据组合数的定义来计算 (n, m)
(res *= cnt(n % mod, m % mod)) %= mod;
n /= mod;
m /= mod;
}
return res;
}
这里附上完整的实现:
class Lucas {
private:
int mod;
std::vector<i64> fa, ifa;
int cnt(int n, int m) {
if (n < m) {
return 0;
}
return fa[n] * ifa[m] * ifa[n - m] % mod;
}
public:
// 根据模数预处理阶乘和阶乘逆元
Lucas(int m): mod(m) {
fa.assign(mod, 1ll);
for (int i = 1; i < mod; i++) {
fa[i] = fa[i - 1] * i % mod;
}
ifa.assign(mod, mod - 1); // 威尔逊定理
for (int i = mod - 1; i > 0; i--) {
ifa[i - 1] = ifa[i] * i % mod;
}
}
int C(int n, int m) {
i64 res = 1;
while (m) {
(res *= cnt(n % mod, m % mod)) %= mod;
n /= mod;
m /= mod;
}
return res;
}
};
虽然本文不是专门讲威尔逊定理的,但是还是在这里解释以下为什么可以根据这个定理将 ifa 赋初始值 mod - 1(实际上只要 ifa[mod - 1] = mod - 1 就够了,其他的值无所谓)。
首先威尔逊定理是:
对于任何素数 \(p\) 有:
\[(p - 1)! + 1 \equiv 0(mod p) \]
即对于素数 \(p\),有: \((p - 1)! \% p = p - 1\)。
那么这和阶乘的逆元有什么关系呢?
记 \((p - 1)!\) 的逆元为 \(I\),则根据逆元的定义,我们应该有:
\[I(p - 1)! \equiv 1(modp)
\]
即(此步用了威尔逊定理):
\[I(p - 1) \% p = 1;
\]
我们发现,\(I = p - 1\) 正好可以满足这个式子——代入这个值得到:
\[(p^2 - 2p + 1) \% p = 1;
\]
所以 \((p - 1)!\) 的在模 \(p\) 时的逆元就是 \((p - 1)\)。
浙公网安备 33010602011771号