1. 程式人生 > >求逆元,逆元的意義

求逆元,逆元的意義

逆元

首先說明逆元的概念,類似於倒數的性質。

方程ax≡1(mod p),的解稱為a關於模p的逆,當gcd(a,p)==1(即a,p互質)時,方程有唯一解,否則無解。

對於一些題目會要求把結果MOD一個數,通常是一個較大的質數,對於加減乘法通過同餘定理可以直接拆開計算,

但對於(a/b)%MOD這個式子,是不可以寫成(a%MOD/b%MOD)%MOD的,但是可以寫為(a*b-1)%MOD,其中b-1表示b的逆元。

知道了逆元的作用,接下來就是逆元的求法。

首先,有一個費馬小定理:

費馬小定理(Fermat’s little theorem)是數論中的一個重要定理,在1636年提出,其內容為: 假如p是質數,且gcd(a,p)=1,那麼 a(p-1)≡1(mod p),即:假如a是整數,p是質數,且a,p互質(即兩者只有一個公約數1),那麼a的(p-1)次方除以p的餘數恆等於1。

意思很明瞭,由上方的公式很容易推匯出:a*a(p-2)≡1(mod p)對於整數a,p,a關於p的逆元就是a^(p-2),直接快速冪解之即可,但注意這個定理要求a,p互質!

逆元 - 組合數取模 現在目標是求Cmn%pCnm%p,p為素數(經典p=1e9+7)

雖然有Cmn=n!m!(n−m)!Cnm=n!m!(n−m)!,但由於取模的性質對於除法不適用,所以Cmn%pCnm%p≠(n!%pm!%p∗(n−m)!%p)%p(n!%pm!%p∗(n−m)!%p)%p 所以需要把“除法”轉換成“乘法”,才能藉助取模的性質在不爆long long的情況下計算組合數。這時候就需要用到“逆元”!

逆元:對於a和p(a和p互素),若ab%p≡1,則稱b為a%p的逆元。 那這個逆元有什麼用呢?試想一下求(ab)(ab)%p,如果你知道b%p的逆元是c,那麼就可以轉變成(ab)(ab)%p = a

c%p = (a%p)(c%p)%p

那怎麼求逆元呢?這時候就要引入強大的費馬小定理!

費馬小定理:對於a和素數p,滿足ap1a^{p-1}%p≡1 接著因為ap−1ap−1 = ap−2∗aap−2∗a,所以有ap−2∗aap−2∗a%p≡1!對比逆元的定義可得,ap−2ap−2是a的逆元!

所以問題就轉換成求解ap−2ap−2,即變成求快速冪的問題了(當然這需要滿足p為素數)。

現在總結一下求解Cmn%pCnm%p的步驟:

通過迴圈,預先算好所有小於max_number的階乘(%p)的結果,存到fac[max_number]裡 (fac[i] = i!%p) 求m!%p的逆元(即求fac[m]的逆元):根據費馬小定理,x%p的逆元為xp−2xp−2,因此通過快速冪,求解fac[m]p−2fac[m]p−2%p,記為M 求(n-m)!%p的逆元:同理為求解fac[n−m]p−2fac[n−m]p−2%p,記為NM Cmn%pCnm%p = ((fac[n]M)%p

NM)%p

//快速冪求x^n%mod 
long long pow_mod(long long x, long long n, long long mod) {
    long long res = 1;
    while (n) {
        if (n & 1) res = res * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return res;
}
long long fac[MAX_NUMBER+5];
long long n, m, p;
int main() {
    while (~scanf("%lld %lld %lld", &n, &m, &p)) {        
        //預處理求fac,fac[i] = i!%p 
        fac[0] = 1;
        for (int i = 1; i <= n; i++) {fac[i] = fac[i - 1] * i % p;}
        //組合數 = n!*(m!%p的逆元)*((n-m)!%p的逆元)%p 
        printf("%lld\n", fac[n] * pow_mod(fac[m], p - 2, p) % p * pow_mod(fac[n - m], p - 2, p) % p);
    }
}