在實現組合數計算的時候要防止溢出

1. 在計算組合數C(N, M)的時候如果利用公式n!/(m!*(n-m)!)的話,很可能會溢出。

    因爲對於階乘,13!已經超過了int能表示的範圍,而且也會很快超過long long的表示範圍。


2. 如果按照定義先計算分子,再計算分母,再相除的話也會溢出。


3. 最保險的計算方式如下:

分子:N*(N-1)*...*(N-M+2)(N-M+1)

分母:M*(M-1)*...*2*1


從後往前計算,先計算(N-M+1)/1=x1,再計算(N-M+2)*x1/2=x2,再計算(N-M+3)*x2/3,這樣能防止溢出,代碼如下:(這樣計算能夠保證每次的計算結果都是整數,因爲每次的計算結果就是一個組合數)

當然如果本來組合數的大小就超過long long能表示的範圍,那下面的代碼也不行了。


#include <iostream>
#include <iomanip>
#include <cmath>
#define PI 3.1415927
using namespace std;

//avoid use fac to calculate c(n,m)
//fac will overflow
long long C(int N, int M) {
	long long sum = 1;
	for(int i=1;i<=M; i++) {
		sum=sum*(N-M+i)/i;
	}
	return sum;
}

//C(N, M)*(M個元素錯排的總數)
long long func(int N, int M) {
	long long num[51]={0};
	num[0]=1;//0個元素錯排,即全部排對了,只有1種可能
	num[1]=0;
	num[2]=1;
	for(int i=3; i<=M; i++) {
		num[i]=(i-1)*(num[i-1]+num[i-2]);
	}
	return C(N,M)*num[M];	
}

int main()
{ 
	int n;
	while(cin >> n) {
		if(n==0) break;
		long long sum = 0;
		for(int i=0; i<=n/2; i++) {//0,1 to n/2 錯排 是符合要求的
			sum+=func(n,i);
		}
		cout << sum << endl;
	}
	return 0;
}

上面的代碼來源於HDUOJ2068題,屬於排列組合+錯排的綜合。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章