1. 程式人生 > >求組合數取模(楊輝三角打表 & 求逆元(擴充套件歐幾里得、費馬小定理、尤拉定理、線性求法) & Lucas)

求組合數取模(楊輝三角打表 & 求逆元(擴充套件歐幾里得、費馬小定理、尤拉定理、線性求法) & Lucas)

    在acm競賽中,組合數取模的題目還是經常會見到的,所以這是有必要掌握的一個演算法。我本人就因為這個東西而被坑了很多次了= =之前的部落格也都扯過了,就不多說了,下面進入正題。

(1)楊輝三角求組合數

    楊輝三角這個東西應該都不陌生,三角的兩邊始終為一,之後向下累加,組成楊輝三角。

    而同樣的,這個三角也可以看作一個組合數的表格,比如第三行中,依次可看作為C(3,0),C(3,1),C(3,2),C(3,3)。而通過這個,我們也可以發現一個組合數的規律,即是C(n,m)=C(n-1,m-1)+C(n-1,m)。所以,對於一些資料比較小的題目,我們可以通過用楊輝三角打表求組合數的方法得到需要的數。

int Combination(int n)
{
     int i,j;
     a[0][0]=1;
     for(i=0;i<=n;i++)
     {
          a[i][0]=a[i][i]=1;
          for(j=1;j<i;j++)
          {
               a[i][j]=(a[i-1][j-1]+a[i-1][j])%mod;
          }
     }
     return 0;
}


(2)逆元求組合數

    而對於一些題目,給出的資料範圍很大,就不能用打表的方式來做,需要直接求出組合數才行,我們根據組合數的定義,可以知道組合數的直接求法。


    但是這種求法也存在著一定的問題,假如數字太大,可能會爆出long long的記憶體範圍,但是取模處理對於除法並不適用,我們無法將直接看作,所以,我們這裡需要用求逆元的方式,將這個除法式子改為乘法式子,才可以進行分配開的取模運算。

    這裡逆元指乘法逆元,假設b存在乘法逆元,即與m互質(充要條件)。設c是b的逆元,即b∗c≡1(mod p),那麼有a/b=(a/b)∗1=(a/b)∗b∗c=a∗c(mod p),即,除以一個數取模等於乘以這個數的逆元取模。這樣就可以將除法計算變為乘法計算,進而進行取模就可以避開出現精度的問題。

    如何求逆元也有很多方法:

    1、當a和p互質時,逆元求解一般利用擴充套件歐幾里得演算法。
    2、當p為質數的時候直接使用費馬小定理,p為非質數使用尤拉函式。
    3、當p為質數的時候,也可使用線性

求[1,p-1]所有數逆元的方法。

1、擴充套件歐幾里得

    歐幾里得演算法都很熟悉,就是gcd(a,b),擴充套件歐幾里得則是一定能找到x和y,使得a*x+b*y=gcd(a,b),當a與b互質的時候,我們可以得到gcd(a,b)=1,a*x+b*y=1,等式兩邊同時mod b,即可得到a*x≡1(mod b),因此x是a%b的逆元。

    而用擴充套件歐幾里得求出乘法逆元則是求出最小的x非負整數解,對於x有通解x0+(b/gcd)*t,因此對於最後的結果%b的絕對值便可得到(以防b為負),如果結果為負,則加上abs(b)便可得到最小的結果。

long long exgcd(long long a,long long b,long long &x,long long &y)
{
    if(!b)
    {
        x=1;
        y=0;
        return a;
    }
    long long ans=exgcd(b,a%b,x,y);
    long long temp=x;
    x=y;
    y=temp-a/b*y;
    return ans;
}

long long cal(long long a,long long b)
{
    long long x,y;
    long long g=exgcd(a,b,x,y);
    if(g!=1)
        return -1;
    long long ans=x%abs(b);
    if(ans<=0)
        ans+=abs(b);
    return ans;
}

2、費馬小定理

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

    根據這個公式,進行一下變形可以得到a(p-2)≡a-1(mod p),所以我們可以說a(p-2)為a的乘法逆元。

    在求組合數取模的時候,可以對階乘先進行預處理,之後再通過費馬小定理來進行計算便可得到要求的逆元。

int getfac()  
{  
    int i;  
    fac[0]=1;  
    for(i=1;i<fcnt;i++)  
    {  
        fac[i]=fac[i-1]*i%mod;  
    }  
    return 0;  
}  
  
long long Pow(long long a,long long b)  
{  
    long long ans=1;  
    while(b)  
    {  
        if(b&1)  
        {  
            b--;  
            ans=(ans*a)%mod;  
        }  
        b>>=1;  
        a=(a*a)%mod;  
    }  
    return ans;  
}  
  
long long inv(long long a)  
{  
    return Pow(a,mod-2);  
}  
  
long long C(long long n,long long m)  
{  
    if(n<m)  
        return 0;  
    return fac[n]*inv(fac[m])%mod*inv(fac[n-m])%mod;  
}  

3、尤拉定理

    在數論,對正整數n,尤拉函式是小於n的正整數中與n互質的數的數目(φ(1)=1)。例如φ(8)=4,因為1,3,5,7均和8互質。
    而尤拉定理表明,若a,p為正整數,且a,p互質,則aφ(p)≡1(mod p)。

    和前面的費馬小定理一樣,對這個式子進行變形,可以得到aφ(p)-1≡a-1(mod p),即aφ(p)-1為a的逆元。

    當出現p不為質數不能用費馬小定理時可以使用,但一般比較少見。

long long euler(int p)
{
    long long ans=p,a=p;
    long long i;
    for(i=2;i*i<=a;i++)
    {
        if(a%i==0)
        {
            ans=ans/i*(i-1);
            while(a%i==0)
                a/=i;
        }
    }
    if(a>1)
        ans=ans/a*(a-1);
    return ans;
}

long long eu=euler(mod)-1;

long long inv(long long a)
{
    return Pow(a,eu);
}

long long C(long long n,long long m)
{
    if(n<m)
        return 0;
    return fac[n]*inv(fac[m])%mod*inv(fac[n-m])%mod;
}

4、線性求逆

inv[1]=1;
for(int i=2;i<=n;i++)
    inv[i]=(p-p/i)*inv[p%i]%p;

/*遞迴
int Get_inv(int n){
    if(n==1)
        return 1;
    return (p-p/n)*(Get_inv(p%n))%p;
}*/

(3)Lucas定理求組合數

    求逆元的方式只適用於較小的數,若資料過大的情況就要用Lucas定理來進行求解。

    具體的定理公式如下:


    所以在求組合數的時候可以通過Lucas定理來遞迴求值,具體的證明過程和轉化表示我也沒看懂= =先記住公式吧......

long long Pow(long long a,long long b)  
{  
    long long ans=1;  
    while(b)  
    {  
        if(b&1)  
        {  
            b--;  
            ans=(ans*a)%p;  
        }  
        b>>=1;  
        a=(a*a)%p;  
    }  
    return ans;  
}  
  
long long C(long long n,long long m)  
{  
    if(n<m)  
        return 0;  
    long long a=1,b=1;  
    while(m)  
    {  
        a=(a*n)%p;  
        b=(b*m)%p;  
        m--;  
        n--;  
    }  
    return (a*Pow(b,p-2))%p;  
}  
  
long long Lucas(long long n,long long m)  
{  
    if(m==0)  
        return 1;  
    return Lucas(n/p,m/p)*C(n%p,m%p)%p;  
}  

(4)注意

    對於大組合數取模C(n,m),n,m不大於10^5的話,用逆元的方法解決。

    對於n,m大於10^5的話,那麼要求p<10^5,這樣就是Lucas定理了,將n,m轉化到10^5以內解。