求組合數取模(楊輝三角打表 & 求逆元(擴充套件歐幾里得、費馬小定理、尤拉定理、線性求法) & 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、擴充套件歐幾里得
歐幾里得演算法都很熟悉,就是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以內解。