1. 程式人生 > >求組合數C(n,m) % mod的幾種方法

求組合數C(n,m) % mod的幾種方法

演算法一:乘法逆元,在m,n和mod比較小的情況下適用

乘法逆元:(a/b)% mod = a * b^(mod-2),mod為素數

_{}^{}\textrm{}C_{n}^{m}\textrm{} = n!/(m!*(n-m)!)=(n*n-1*...*m+1)/m!= (n*n-1*...*m+1)*m^{mod-2}

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#define LL long long

using namespace std;

const int MOD = 9982;

LL pow(LL x)
{
	LL n = MOD-2; 
    LL res=1;
	while(n>0)
	{
	   if(n & 1)	
	   	 res=res*x%MOD;
	   x=x*x%MOD;
	   n>>=1;
	}
	return res%MOD;	
}
  
LL C(LL n,LL m)  
{  
    if(m < 0)return 0;  
    if(n < m)return 0;  
    if(m > n-m) m = n-m;  

    LL up = 1, down = 1;  
    for(LL i = 0 ; i < m ; i ++){  
        up = up * (n-i) % MOD;  //分子 
        down = down * (i+1) % MOD;  //分母 
    }  
    return up * pow(down) % MOD;  //乘法逆元 
}  

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	LL ans = C(n,m)%MOD; 
	printf("%lld\n",ans);
	return 0;
}

演算法二:Lucas定理 + 乘法逆元,適用於mod為素數且大小為10^5左右

Lucas定理:A、B是非負整數,p是質數。A B寫成p進位制:A=a[n]a[n-1]…a[0],B=b[n]b[n-1]…b[0]。 
則組合數C(A,B)與C(a[n],b[n])C(a[n-1],b[n-1])…*C(a[0],b[0]) mod p同餘 
即:Lucas(n,m,p)=C(n%p,m%p)*Lucas(n/p,m/p,p)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#define LL long long

using namespace std;

const int MOD = 100000;

LL pow(LL x)
{
    LL n = MOD-2; 
    LL res=1;
    while(n>0)
    {
       if(n & 1)    
            res=res*x%MOD;
       x=x*x%MOD;
       n>>=1;
    }
    return res%MOD;    
}

LL C(LL n,LL m)  
{  
    if(m < 0)return 0;  
    if(n < m)return 0;  
    if(m > n-m) m = n-m;  

    LL up = 1, down = 1;  
    for(LL i = 0 ; i < m ; i ++){  
        up = up * (n-i) % MOD;  //分子 
        down = down * (i+1) % MOD;  //分母 
    }  
    return up * pow(down) % MOD;  //乘法逆元 
} 


//當n和m比較大,mod是素數且比較小的時候(10^5左右),通過Lucas定理計算
LL Lucas(LL n, LL m)
{
    if(m == 0)    return 1;
    return C(n%MOD,m%MOD)*Lucas(n/MOD,m/MOD)%MOD;    
} 

int main()
{
    int T,n,m;
    LL ans;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        ans = Lucas(n,m)%MOD;
        printf("%lld\n",ans);
    } 
    return 0;
}
 

演算法三:預處理 + 乘法逆元,適用於mod為素數且比較大的時候(超過10^5)

預處理的時候注意: m!的MOD次方 = (m-1)!的MOD次方 * m的MOD次方

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#define LL long long
#define maxn 1000000

using namespace std;

const int MOD = 998244353;

LL fact[maxn+5];	//階乘 
LL a[maxn+10];	// 乘法逆元 
//LL inv[maxn+10];	//快速冪 

LL pow(LL x)
{
	LL n = MOD-2;
    LL res=1;
	while(n>0)
	{
	   if(n%2==1)	
	   	 res=res*x%MOD;
	   x=x*x%MOD;
	   n>>=1;
	}
	return res;	
}

void init(){
    a[0] = a[1] = 1;
    fact[0] = fact[1] = 1;
//  inv[1] = 1;
    for(int i = 2; i <= 1000005; i++)
    {
        fact[i] = fact[i-1] * i % MOD;
		a[i] = a[i-1] * pow(i) % MOD;	//m!的MOD次方 = (m-1)!的MOD次方 * m的MOD次方 
//      inv[i] = (MOD - MOD/i)*inv[MOD%i]%MOD;
//      a[i] = a[i-1] * inv[i] % MOD;	
    }
}

LL C(int n, int m){	//乘法逆元 
	if(n<0||m<0||n<m)return 0;
    return fact[n]*a[n-m]%MOD*a[m]%MOD;
}

int main()
{
	int T,n,m;
	LL ans;
	init();//預處理 
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		ans = C(n,m)%MOD;
		printf("%lld\n",ans);
	} 
	return 0;
}