Lucas 定理
该定理是用来求当
(nm)
(
n
m
)
中的
m,n
m
,
n
很大而
p
p
为素数时, 的值。
Lucas 定理:令
n=sp+q,m=tp+r.(q,r<p)
n
=
s
p
+
q
,
m
=
t
p
+
r
.
(
q
,
r
<
p
)
那么:
则在编程时,只需继续对 (st) ( s t ) 应用 Lucas 定理即可。代码可以递归地完成这个过程,终止条件是 t=0 t = 0 。时间复杂度为 O(logpn∗p) O ( log p n ∗ p ) 。
大组合数取余
应用 Lucas 定理,可以将求
(nm)(modp)
(
n
m
)
(
mod
p
)
的问题转化为求
(qr)(st)(modp)
(
q
r
)
(
s
t
)
(
mod
p
)
的问题,因为
q,r<p
q
,
r
<
p
可对
(qr)(modp)
(
q
r
)
(
mod
p
)
直接求组合数,而对
(st)(modp)
(
s
t
)
(
mod
p
)
递归使用 Lucas 定理求值。
而对于小组合数有:
对于阶乘很大的情况,计算机编程时可能需要对分子分母分别取余,但是对于除法不能轻易使用同余定理(只在 +,−,∗ + , − , ∗ 三种运算下有效),所以希望可以将除法取余转换为等价的乘法取余,这时便需要另外重要的知识:逆元以及费马小定理。
逆元
逆元定义:对于正整数 a a 和 ,如果有 ax≡1(modp) a x ≡ 1 ( mod p ) ,那么把这个同余方程中 x x 的最小正整数解叫做 的逆元。
费马小定理
定义:假如 p p 是质数,且,即 a,p a , p 互质,那么 ap−1≡1(modp) a p − 1 ≡ 1 ( mod p )
由费马小定理可得:
因此当 a,p a , p 互质且 p p 为素数时,有:
带入组合数公式,即有:
这样便将除数求余转化为同余的乘法求余运算。
这里出现了求幂运算 ap−2 a p − 2 ,可以使用快速求幂算法
快速求幂
//cpp
LL quickPow(LL n,LL m){
LL ans = 1;
n %= mod;
while(m){
if(m & 1)
ans = ans * n % mod;
n = n * n % mod;
m >>= 1;
}
return ans;
}
对于求阶乘,可以使用一个全局数组缓存结果,这样就不用每次求了
阶乘
//cpp
void getFac(int n){
fac[0]=fac[1]=1;
for(int i=2;i<=n;++i){
fac[i]=fac[i-1] * i % mod;
}
}
下面给出求组合数和Lucas定理的代码实现
求组合数
//cpp
LL C(LL n,LL m){
if(m>n)
return 0;
return fac[n]*quickPow(fac[m]*fac[n-m],mod-2) % mod;
}
Lucas 定理
//cpp
LL Lucas(LL n,LL m){
if(m == 0)
return 1;
return C(n%mod,m%mod)*Lucas(n/mod,m/mod) % mod;
}
参考资料
[1] https://blog.youkuaiyun.com/wyg1997/article/details/52152282 “Lucas定理 & 逆元学习小结”
[2] https://baike.baidu.com/item/%E8%B4%B9%E9%A9%AC%E5%B0%8F%E5%AE%9A%E7%90%86 “费马小定理”
[3] https://baike.baidu.com/item/lucas/4326261?fr=aladdin “Lucas 定理”