逆元 - 組合數取模


現在目標是求$C_n^m\%p$,p為素數(經典p=1e9+7)

雖然有$C_n^m=\frac{n!}{m!(n-m)!}$,但由於取模的性質對於除法不適用,所以$C_n^m\%p$≠$( \frac{n!\%p}{m!\%p*(n-m)!\%p} )\%p$

所以需要把“除法”轉換成“乘法”,才能借助取模的性質在不爆long long的情況下計算組合數。這時候就需要用到“逆元”!

 逆元:對於a和p(a和p互素),若a*b%p≡1,則稱b為a%p的逆元。

那這個逆元有什么用呢?試想一下求$(\frac{a}{b})$%p,如果你知道b%p的逆元是c,那么就可以轉變成$(\frac{a}{b})$%p = a*c%p = (a%p)(c%p)%p

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

 費馬小定理:對於a和素數p,滿足$a^{p-1}$%p≡1

接着因為$a^{p-1}$ = $a^{p-2}*a$,所以有$a^{p-2}*a$%p≡1!對比逆元的定義可得,$a^{p-2}$是a的逆元!

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

現在總結一下求解$C_n^m\%p$的步驟:

  1. 通過循環,預先算好所有小於max_number的階乘(%p)的結果,存到fac[max_number]里 (fac[i] = i!%p)
  2. 求m!%p的逆元(即求fac[m]的逆元):根據費馬小定理,x%p的逆元為$x^{p-2}$,因此通過快速冪,求解$fac[m]^{p-2}$%p,記為M
  3. 求(n-m)!%p的逆元:同理為求解$fac[n-m]^{p-2}$%p,記為NM
  4. $C_n^m\%p$ = ((fac[n]*M)%p*NM)%p

 

用C++實現的代碼,輸入為n,m,p,要求0<=m<=n<=1e6(否則fac存不下),且gcd(p,n!)=1(即互素),輸出為$C_n^m\%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);
    }
}

 

另一種求法以后有空再寫...


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM