看了看网上关于 L u c a s Lucas Lucas 的讲解,感觉很多充斥着大量难懂的公式,对于小学生初学者来说不太友好,于是决定写一篇没有任何算法数学基础的人都能看懂的 L u c a s Lucas Lucas 。(当然本文中也会出现一些公式,不过没有 ∑ \sum ∑ 这种令人难受的符号而且公式大多特别简单)
L u c a s Lucas Lucas 定理主要用于大组合数取膜。
他的结论很简单:当且仅当 p p p 为质数时, C m n m o d p = C m m o d p n m o d p × C m ÷ p n ÷ p m o d p C_m^n\, mod\,p = C_{m\,mod\,p}^{n\, mod\, p} \times C_{m\div p}^{n\div p}\,mod\,p Cmnmodp=Cmmodpnmodp×Cm÷pn÷pmodp
由于作者太弱并不会证明,如果对恶心的证明感兴趣可以左转百度。
在 n , m n,m n,m 很大而 p p p 又很小的时候就可以使用 L u c a s Lucas Lucas 定理。
写个递归就可以实现 L u c a s Lucas Lucas 定理啦(本文所有代码均默认已经添加了typedef long long ll)!
inline ll Lucas(ll m, ll n) { return n == 0 ? 1 : C(m % p, n % p) * Lucas(m / p, n / p) % p; }慢着,你以为这就完了?
L u c a s Lucas Lucas 函数已经很优秀了,可是组合数如果按照杨辉三角计算…… L u c a s Lucas Lucas 复杂度+n。
按公式计算呢?炸了。
这里科普一个叫逆元的东西,它是拿来干啥的呢?
我们知道:
( a + b ) m o d p = ( a m o d p + b m o d p ) m o d p (a+b)\,mod\,p=(a\,mod\,p+b\,mod\,p)\,mod\,p (a+b)modp=(amodp+bmodp)modp ( a + b ) m o d p = ( a m o d p − b m o d p + p ) m o d p (a+b)\,mod\,p=(a\,mod\,p-b\,mod\,p + p)\,mod\,p (a+b)modp=(amodp−bmodp+p)modp ( a × b ) m o d p = ( a m o d p × b m o d p ) m o d p (a\times b)\,mod\,p=(a\,mod\,p\times b\,mod\,p)\,mod\,p (a×b)modp=(amodp×bmodp)modp
可是除法……就很讨厌了,他不满足模运算的“分配律”。
那么,我们可以考虑将除法强行变成除法:
( a ÷ b ) m o d p = ( a m o d p × b ‘ m o d p ) m o d p (a\div b)\,mod\,p=(a\,mod\,p\times b`\,mod\,p)\,mod\,p (a÷b)modp=(amodp×b‘modp)modp
这里的 b ‘ b` b‘ 就是 b b b 的逆元。
逆元定义:若存在 a × x m o d p = 1 a\times x\,mod\,p=1 a×xmodp=1,则称 x x x 为 a a a 在膜 p p p 意义下的逆元。
求逆元一般有三种方法: e x g c d exgcd exgcd,费马小定理,线性求逆。其中费马小定理是最简单的一种方法。
费马小定理:若 g c d ( a , p ) = 1 gcd(a,p)=1 gcd(a,p)=1,则 p ∣ a p − 1 − 1 p\vert a^{p-1}-1 p∣ap−1−1,即 p p p 整除 a p − 1 − 1 a^{p-1}-1 ap−1−1。也就是说, a p − 1 − 1 m o d p = 1 a^{p-1}-1\,mod\,p=1 ap−1−1modp=1。这不就是逆元的定义?于是我们可以得出 a a a 的逆元: a p − 2 m o d p a^{p-2}\,mod\,p ap−2modp。
用 e x g c d exgcd exgcd 可以证明,逆元方程仅当 g c d ( a , p ) = 1 gcd(a,p)=1 gcd(a,p)=1 时有解,否则无解。这部分的证明如果不想看可以跳到加粗的”结论“后面的“所以”。
∵ a ⋅ x m o d p = 1 ∴ a ⋅ x − 1 m o d p = 0 ∴ a ⋅ x − 1 = p ⋅ y ( y ∈ N ) ∴ a ⋅ x − p ⋅ y = 1 ∴ a ⋅ x + b ⋅ y = 1 ( b = − p ) \because a\cdot x\,mod\,p=1\\ \therefore a\cdot x - 1\,mod\,p=0\\ \therefore a\cdot x - 1=p\cdot y(y\in N)\\ \therefore a\cdot x-p\cdot y=1\\ \therefore a\cdot x+b\cdot y = 1(b=-p) ∵a⋅xmodp=1∴a⋅x−1modp=0∴a⋅x−1=p⋅y(y∈N)∴a⋅x−p⋅y=1∴a⋅x+b⋅y=1(b=−p)
下面对方程 a ⋅ x + b ⋅ y a\cdot x+b\cdot y a⋅x+b⋅y 进行讨论:
若 有 解 a ⋅ x 0 + b ⋅ y 0 如 果 还 有 解 a ⋅ x 1 + b ⋅ y 1 则 a ⋅ x 0 + b ⋅ y 0 = a ⋅ x 1 + b ⋅ y 1 移 项 得 a ⋅ ( x 0 − x 1 ) = b ⋅ ( y 1 − y 0 ) 将 方 程 两 边 同 时 除 以 g c d ( a , b ) 得 a 0 ⋅ ( x 0 − x 1 ) = b 0 ⋅ ( y 1 − y 0 ) 此 时 a 0 和 b 0 互 素 。 因 此 b 0 ∣ x 0 − x 1 设 k ⋅ b 0 = x 0 − x 1 则 y 1 − y 0 = k ⋅ a 0 若有解a\cdot x0+b\cdot y0\\ 如果还有解a\cdot x1+b\cdot y1\\ 则a\cdot x0+b\cdot y0 = a\cdot x1+b\cdot y1\\ 移项得a\cdot(x0-x1)=b\cdot (y1-y0)\\ 将方程两边同时除以gcd(a,b)\\ 得a0\cdot(x0-x1)=b0\cdot (y1-y0)\\ 此时a0和b0互素。因此 b0\vert x0-x1\\ 设 k\cdot b0=x0-x1\\ 则 y1-y0 = k\cdot a0 若有解a⋅x0+b⋅y0如果还有解a⋅x1+b⋅y1则a⋅x0+b⋅y0=a⋅x1+b⋅y1移项得a⋅(x0−x1)=b⋅(y1−y0)将方程两边同时除以gcd(a,b)得a0⋅(x0−x1)=b0⋅(y1−y0)此时a0和b0互素。因此b0∣x0−x1设k⋅b0=x0−x1则y1−y0=k⋅a0
结论: 设 a , b , c a,b,c a,b,c 为任意整数,若方程 a ⋅ x + b ⋅ y = c a\cdot x + b\cdot y=c a⋅x+b⋅y=c 的一组整数解为 ( x 0 , y 0 ) (x0,y0) (x0,y0),则它的任意整数解都可以写成 ( x 0 + k ⋅ b 0 , y 0 − k ⋅ a 0 ) (x0+k\cdot b0,y0-k\cdot a0) (x0+k⋅b0,y0−k⋅a0),其中 a 0 = a / g c d ( a , b ) , b 0 = b / g c d ( a , b ) a0=a/gcd(a,b),b0=b/gcd(a,b) a0=a/gcd(a,b),b0=b/gcd(a,b), k k k 取任意整数。
所以,上文的逆元方程要有解,当且仅当 1 1 1 是 g c d ( a , p ) gcd(a,p) gcd(a,p) 的倍数,即 g c d ( a , p ) = 1 gcd(a,p)=1 gcd(a,p)=1。
然后快速冥求出 a p − 2 m o d p a^{p-2}\,mod\,p ap−2modp 就行了?
快速冥求法:对于 a b a^b ab,当 b b b 为奇数时 a b = a ( b − 1 ) / 2 × a ( b − 1 ) / 2 m o d p × a m o d p a^b=a^{(b-1)/2}\times a^{(b-1)/2}\,mod\,p\times a\,mod\,p ab=a(b−1)/2×a(b−1)/2modp×amodp。否则等于 a b / 2 × a b / 2 m o d p a^{b/2}\times a^{b/2}\,mod\,p ab/2×ab/2modp。
写个迭代就可以求出快速冥和逆元了!
inline ll quickpow(ll a, ll b) { register ll res(1); a %= p; while (b) { if (b & 1) res = res * a % p; a = a * a % p; b >>= 1; } return res % p; } inline ll inv(ll a, ll p) {//求a在膜p意义下的逆元 return quickpow(a, p - 2); }组合数代码:
inline ll C(ll m, ll n) { return m < n ? 0 : fact[m] * inv(fact[n], p) % p * inv(fact[m - n], p) % p;//逆元取模,fact[i]表示i的阶乘,如果p大于1e6必须现算阶乘 }完整代码:
//输入n,m,p,求C{n,m}%p(p<=1000000) #include <cstdio> typedef long long ll; ll fact[1000005], p; inline void Init() { fact[1] = 1; for (register int i(2); i <= p; ++ i) fact[i] = fact[i - 1] * i % p; } inline ll quickpow(ll a, ll b) { register ll res(1); a %= p; while (b) { if (b & 1) res = res * a % p; a = a * a % p; b >>= 1; } return res % p; } inline ll inv(ll a, ll p) { return quickpow(a, p - 2); } inline ll C(ll m, ll n) { return m < n ? 0 : fact[m] * inv(fact[n], p) % p * inv(fact[m - n], p) % p; } inline ll Lucas(ll m, ll n) { return n == 0 ? 1 : C(m % p, n % p) * Lucas(m / p, n / p) % p; } int main() { ll n, m; scanf("%lld%lld%lld", &n, &m, &p); Init(); printf("%lld", Lucas(n, m)); }最后别忘了 L u c a s Lucas Lucas 只适用于模数为质数且不超过1e6的情况,如果模数可能为合数要用的作者不会的扩展 L u c a s Lucas Lucas 定理。代码时间复杂度为 O ( p l o g p ) O(plog_p) O(plogp),如果加个线性求逆元的预处理可以达到 O ( p + l o g p n ) O(p+log_pn) O(p+logpn)
