1. 程式人生 > >組合數計算總結

組合數計算總結

一、  一般組合數計算 

利用 的據算公式進行運算,簡單明瞭。利用乘法與除法的同時執行,有效地降低溢位(除法能整除的保證:每i個數中必有一個是i的倍數)。只能對最後的結果進行取模,無法在運算時取模,雖然求取的時候轉化為了ans* ans’ * ans’’……的形式,雖然連乘運算可以逐步取模,但如果對ans取模,將無法保證下一步的除法能整除,所以不可取。

時間複雜度:O(m)

mod的要求:無

有效範圍:1<=n,m<=60

——————————————————————————————————

//一般方法求C(n,m)最後取模。C(62,28)溢位。有效範圍1<n,m<=60
ll CNormal(int n, int m)
{
	if ( m>n ) return 0;
	ll ans = 1;
	for (int i=1; i<=m; i++)
	{
		ans *= (n – m + i);
		ans /= i;
	}
	return ans % mod;
}

Ps:ans *= (n – m + i)也可用ans *= (n – I + 1),但前者更小

二、  楊輝三角計算組合數

利用楊輝三角與組合數之間的關係進行逐步求解組合數,彌補了中間結果無法取模的缺點,但時間複雜度高。可以逐步取模,無溢位。

時間複雜度:O(n*m)

mod的要求:無

有效範圍:1<=n,m<=1000

——————————————————————————————————

//楊輝三角求C(n,m)
ll matrix[110][110]={0};
ll CMatrix(int n, int m)
{
	if ( m>n ) return 0;
	ll ans = 0;
	matrix[1][0] = matrix[1][1] = 1;
	for (int i=2; i<=n; i++)
	{
		int k = min(i, m);
		for(int j=0; j<=k; j++)
		{
			if ( j==0 || j==i )	matrix[i][j] = 1;
			else matrix[i][j] = (matrix[i-1][j] + matrix[i-1][j-1]) % mod;
		}
	}
	return matrix[n][m];
}


三、  利用乘法逆元求組合數

逆元定義:a*b=1,則稱b為a的乘法逆元。

而在同一個mod下,可以使a的逆元為一個整數,a*b%mod ≡1,則b為a在mod下的乘法逆元。由此,可將 計算公式轉化為全為乘法的式子,如此,便可以實現逐步取模

由費馬小定理知,p是質數,且Gcd(a,p)=1,那麼a^(p-1) ≡ 1(modp),因此易知a*a^(p-2)= 1(mod p),則,a^(p-1)即為a在p下的乘法逆元。

時間複雜度:O(m*log(m))

mod的要求:mod為素數,且m<modGCD(mod, m)=1

有效範圍:1<=n,m<=10^6

——————————————————————————————————

//獲得a在mod下的乘法逆元
ll GetInverse(ll a, ll mod)
{
	ll ans = 1, n = mod - 2;
	a %= mod;
	while ( n )
	{
		if ( n&1 ){
			ans = ans * a % mod;
		}
		n >>= 1;
		a = a * a % mod;
	}
	return ans;
}

//逆元求C(n,m)%mod
ll C(ll n, ll m)
{
	if ( m>n ) return 0;
	ll ans = 1;
	for (int i=1; i<=m; i++)
	{
		ll a = (n + i - m) % mod;
		ll b = i % mod;
		ans = ans * (a * GetInverse(b, mod) % mod) % mod;
	}
	return ans;
}

四、  分解因子求組合數

,利用同底數的指數的除法即指數相減的原理講式子轉化為乘法,從而實現逐步取模。即求出n!,(n-m)!,m!中的所有素數因子並計算。

時間複雜度:O(n)

mod的要求:無(可為合數)

有效範圍:1<=n,m,p<=10^6

——————————————————————————————————

// 求素數表
int prime[1000000] = {0};
bool isPrime[1000000];
void getPrime(int maxn)
{
	int n = 0;
	memset(isPrime, true, sizeof(isPrime));
	for (int i=2; i<=maxn; i++)
	{
		if ( isPrime[i] )
		{
			prime[n++] = i;
			for (int j=i*2; j<=maxn; j+=i) isPrime[j] = false;
		}
	}
}

//快速冪
long long myPow(long long a,long long b)
{
	long long r=1, base=a % mod;
	while ( b )
	{
		if ( b&1 ){
			r *= base;
			r %= mod;
		}
		base *= base;
		base %= mod;
		b >>= 1;
	}

	return r;
}

//獲得n!中因子p的個數
ll getPNum(ll n, ll p)
{
	ll ans = 0;
	while ( n )
	{
		ans += n / p;
		n /= p;
	}
	return ans;
}

//利用因子求C(n,m)
ll CFactor(ll n, ll m)
{
	if ( m>n ) return 0;
	ll ans = 1;
	for (int i=0; prime[i]!=0 && prime[i]<=n; i++)
	{
		ll a = getPNum(n, prime[i]);
		ll b = getPNum(m, prime[i]);
		ll c = getPNum(n - m, prime[i]);
		a -= (b + c);
		ans *= myPow(prime[i], a);
		ans %= mod;
	}
	return ans;
}

五、  Lucas定理*

Lucas定理:如果p為素數,且

                                  

                                  

                     那麼     

由此定理,再結合乘法逆元就可以實現在逐步取模的基礎上將計算量大大減少,且可以保證所有的 ,即 與p互質,從而避免了乘法逆元不存在的特殊情況。此方法在p較小的時候能發揮很大的作用。

時間複雜度:

mod的要求:素數

有效範圍:1<=n,m,p<=10^9

——————————————————————————————————

//利用lucas定理求C(n,m)
ll CLucas(ll n, ll m)
{
	//if ( m==0 ) return 1;
	//return C(n % mod, m % mod) * CLucas(n / mod, m / mod) % mod;

	ll ans = 1;
	while ( m )
	{
		ans = ans * C(n % mod, m % mod) % mod;
		n /= mod;
		m /= mod;
	}
	return ans;
}

半預處理型

由於Lucas定理保證了階乘的數均小於p,所以可以講所有的階乘先預處理,優化C(n,m)

mod的要求:p<10^6,且為素數

有效範圍:1<=n,m<=10^9

——————————————————————————————————

//半預處理
const ll MAXN = 100000;
ll fac[MAXN+100];
void init(int mod)
{
	fac[0] = 1;
	for (int i=1; i<mod; i++){
		fac[i] = fac[i-1] * i % mod;
	}
}

//半預處理逆元求C(n,m)%mod
ll C(ll n, ll m)
{
	if ( m>n ) return 0;
	return fac[n] * (GetInverse(fac[m]*fac[n-m], mod)) % mod;
}
 

全預處理型

若資料組數較多,且p較小(<10^4),則可以預處理所有可能的階乘並進行計算

mod的要求:p<10^4,且為素數

有效範圍:1<=n,m<=10^9

——————————————————————————————————

//階乘預處理
int prime[10000+10] = {0};
bool isPrime[10000+10];
int inverse[10000][1250] = {0};
int fac[10000][1250] = {0};
void init(int maxn)
{
	int n = 0;
	memset(isPrime, true, sizeof(isPrime));
	for (int i=2; i<maxn; i++)
	{
		if ( isPrime[i] )
		{
			prime[i] = n;
			for (int j=i*2; j<maxn; j+=i) isPrime[j] = false;

			//計算逆元
			inverse[0][n] = inverse[1][n] = 1;
			for (int j=2; j<i; j++)
			{
				inverse[j][n] = (i - i/j) * inverse[i%j][n] % i;
			}

			//預處理階乘
			fac[0][n] = fac[1][n] = 1;
			for (int j=2; j<i; j++)
			{
				fac[j][n] = j * fac[j-1][n] % i;//普通階乘
				inverse[j][n] = inverse[j][n] * inverse[j-1][n] % i;//逆元階乘
			}

			n++;
		}
	}
}


//全預處理求C(n,m)%mod
ll C(int n, int m)
{
	if ( m>n ) return 0;
	int num = prime[i];
	return (ll)fac[n][num] * inverse[m][num] % i * inverse[n-m][num] % i;
}