組合數的幾種計算方法


組合數一種是OI中比較常用的知識

除了實際的分析之外,我們要考慮的,就是如何快速計算組合數

下面介紹幾種常用的計算組合數的方法


朴素公式法

顧名思義,直接套公式
int C(int n,int m){
	int ans=1;
	for(int i=1;i<=m;i++) ans=ans*(n-m+i)/i;
	return ans;
}

如果要對質數P取模,就是這樣:
int C(int n,int m){
	int ans=1;
	for(int i=1;i<=m;i++) ans=(LL)ans*(n-m+i)*inverse(i)%P;
	return ans;
}
其中inverse是i對P的逆元,如果P是質數inverse(i)=i^(P-2)   【費馬小定理得】,否則用exgcd求出【歐拉定理也行】


遞推式法

C(n,m)=C(n-1,m-1)+C(n-1,m)
邊界:C(i,0)=C(i,i)=1
這個遞推式用到了動態規划的思想,對於第m個物品,有取和不取兩種情況
可以在O(n^2)的時間內算出所有的C(n,m)

void cal(){
	C[0][0]=1;
	for(int i=1;i<=n;i++){
		C[i][0]=C[i][i]=1;
		for(int j=1;j<=(i>>1);j++){
			C[i][j]=C[i][i-j]=(C[i-1][j-1]+C[i-1][j])%P;
		}
	}
}


Lucas定理


對於n和m比較大(<=10^18)的組合數,就要用到LUCAS定理啦


Lucas定理解決的是n,m比較大而p是小於100000質數


簡而言之就是Lucas(n,m)=C(n%p,m%p)*Lucas(n/p,m/p)%p;

其中組合數C是用任意一種計算10五次方內取模的組合數計算

比如可以預處理階乘fac[i],然后直接C(n,m)=fac[n]*quickpow(fac[n-m]*fac[m],p-2)%p;

或者O(n)套公式直接算也可以

要注意n可能小於m,因為是取模后的結果,這個時候返回0【不然會RE】

 

LL Lucas(LL n,LL m){  
    if(n<m||!m) return 1;  
    return C(n%P,m%P)*Lucas(n/P,m/P)%P;  
} 


 

配合Lucas定理,由於n和m都在10^5范圍內,使用預處理階乘的方法再好不過了:


預處理階乘法


其實就是直接套公式,只不過使用了 預處理階乘+逆元,查詢復雜度O(log100000),非常低

void cal(){  
    fac[0]=1;  
    for(int i=1;i<=P;i++) fac[i]=fac[i-1]*i%P;  
}  
  
inline LL qpow(LL a,LL b){  
    LL ans=1;  
    for(;b;b>>=1,a=a*a%P) if(b&1) ans=ans*a%P;
    return ans;  
}  
  
inline LL C(LL n,LL m){  
    if(n<m) return 0;  
    return fac[n]*qpow(fac[n-m]*fac[m]%P,P-2)%P;  
}  




 


免責聲明!

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



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