盧卡斯定理(十分鐘帶你看懂)
在開始之前我們先介紹3個定理:
1.乘法逆元
如果ax≡1 (mod p),且gcd(a,p)=1(a與p互質),則稱a關於模p的乘法逆元為x。
2.費馬小定理:
3.擴充套件歐幾里得
已知整數a、b,擴充套件歐幾里得演算法可以在求得a、b的最大公約數的同時,能找到整數x、y(其中一個很可能是負數),使它們滿足貝祖等式ax + by = gcd(a, b)。
好了,在明白上面的定理後我們開始分析乘法逆元:ax≡1 (mod p) 這個等式用中文描述就是 a乘一個數x並模p等於1,即 a%p*x%p=res,res%p=1;看上去就是同餘定理的一個簡單等式- -。那麼問題來了。
———————————————————————————————————————————–
為什麼可以用費馬小定理來求逆元呢?
由費馬小定理 , 變形得 ,答案已經很明顯了:若a,p互質,因為且,則,用快速冪可快速求之。
———————————————————————————————————————————–
為什麼可以用擴充套件歐幾里得求得逆元?
我們都知道模就是餘數,比如12%5=12-5*2=2,18%4=18-4*4=2。(/是程式運算中的除)
那麼ax≡1 (mod p)即ax-yp=1.把y寫成+的形式就是ax+py=1,為方便理解下面我們把p寫成b就是ax+by=1。就表示x是a的模b乘法逆元,y是b的模a乘法逆元。然後就可以用擴充套件歐幾里得求了。
———————————————————————————————————————————–
知道逆元怎麼算之後,那麼乘法逆元有什麼用呢?
先說重點,本人認為!乘法逆元最大的作用就是,在要除以一個數,再取模時,把除法變成乘法運算,然後再取模。因為除法,比如用16/5應該是3.2,但是計算機會算成3.。。誤差有沒有,用double就更不用說了,數大了一定有誤差,所以,有了逆元!!!!
若對於數字A,C 存在X,使A * X = 1 (mod C) ,那麼稱X為 A 對C的乘法逆元。
逆元的作用?讓我們來看下面的例子:
12 / 4 mod 7 = ? 很顯然結果是3
我們現在對於數對 (4,7), 可以知道 X = 2是 4 對7的乘法逆元即2*4=1(mod 7)
那麼我們有(12 / 4) * (4 * 2 ) = (?) * (1) (mod 7)
除法被完美地轉化為了乘法
理論依據:
F / A mod C = ?
如果存在 A*X = 1 (mod C)
那麼2邊同時乘起來,得到 F * X = ? (mod C)
成立條件
(1) 模方程 A * X = 1(mod C) 存在解
(2) A | F (F % A == 0)
以下來百度百科:
若ax=1 mod f 則稱a關於模f的乘法逆元為x。也可表示為ax≡1(mod f)。
當a與f互素時,a關於模f的乘法逆元有唯一解。如果不互素,則無解。如果f為素數,則從1到f-1的任意數都與f互素,即在1到f-1之間都恰好有一個關於模f的乘法逆元。
例如,求5關於模14的乘法逆元:
14=5*2+4
5=4+1
說明5與14互素,存在5關於14的乘法逆元。
1=5-4=5-(14-5*2)=5*3-14
因此,5關於模14的乘法逆元為3。
———————————————————————————————————————————–
接下來進入正題:
Lucas定理針對該取值範圍較大又不太大的情況
定理描述
這樣將組合數的求解分解為小問題的乘積,下面考慮計算C(ni, mi) %p.
已知C(n, m) mod p = n!/(m!(n - m)!) mod p。當我們要求(a/b)mod p的值,且b很大,無法直接求得a/b的值時,我們可以轉而使用乘法逆元k,將a乘上k再模p,即(a*k) mod p。 其結果與(a/b) mod p等價。
下面附上Lucas定理的一種證明,見下圖,參考馮志剛《初等數論》第37頁。
對於該定理的理解,也很好理解:
其實大可不必看中間一大堆文字描述,直接看下面公式的推導:
———————————————————————————————————————————–
程式碼實現:
費馬小定理實現(其實跟拓展gcd沒多大區別,換了個公式而已)
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
ll mulit(ll a,ll b,ll m){
ll ans=0;
while(b){
if(b&1) ans=(ans+a)%m;
a=(a<<1)%m;
b>>=1;
}
return ans;
}
ll quick_mod(ll a,ll b,ll m){
ll ans=1;
while(b){
if(b&1){
ans=mulit(ans,a,m);
}
a=mulit(a,a,m);
b>>=1;
}
return ans;
}
ll comp(ll a,ll b,ll m){
if(a<b) return 0;
if(a==b) return 1;
if(b>a-b) b=a-b;
ll ans=1,ca=1,cb=1;
for(int i=0;i<b;i++){
ca=ca*(a-i)%m;
cb=cb*(b-i)%m;
}
ans=ca*quick_mod(cb,m-2,m)%m;
return ans;
}
ll lucas(ll a,ll b,ll m){
ll ans=1;
while(a&&b){
ans=(ans*comp(a%m,b%m,m))%m;
a/=m;
b/=m;
}
return ans;
}
int main()
{
ll a,b,m;
while(cin>>a>>b>>m){
cout<<lucas(a,b,m)<<endl;
}
return 0;
}
拓展gcd實現
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
ll exgcd(ll a,ll b,ll& x,ll& y){
if(a%b==0){
x=0,y=1;
return b;
}
ll r,tx,ty;
r=exgcd(b,a%b,tx,ty);
x=ty;
y=tx-a/b*ty;
}
ll comp(ll a,ll b,ll m){
if(a<b) return 0;
if(a==b) return 1;
if(b>a-b) b=a-b;
ll ans=1,ca=1,cb=1;
for(int i=0;i<b;i++){
ca=ca*(a-i)%m;
cb=cb*(b-i)%m;
}
ll x,y;
exgcd(cb,m,x,y);
x=(x%m+m)%m;
ans=ca*x%m;
return ans;
}
ll lucas(ll a,ll b,ll m){
ll ans=1;
while(a&&b){
ans=(ans*comp(a%m,b%m,m))%m;
a/=m;
b/=m;
}
return ans;
}
int main()
{
ll a,b,m;
int n;
cin>>n;
while(n--){
cin>>a>>b>>m;
cout<<lucas(a+b,b,m)<<endl;
}
return 0;
}