1. 程式人生 > >盧卡斯定理(十分鐘帶你看懂)

盧卡斯定理(十分鐘帶你看懂)

在開始之前我們先介紹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;
}