组合数的计算(对溢出处理)

组合数的计算(对溢出处理)

组合数的计算过程中可能存在连用关键字long long int定义的变量都存放不下的大数,事实上,数字达到一定程度时,可以说非常容易溢出,long long int变量能存放的数字是无法改变的,毕竟long long int范围为0~2^63-1,但是这一些情况下,能在过程中进行约分从而避免了溢出,上面说的无法改变的那种情况就是即使约分了,但由于数字很多,相乘依然会爆

经过我的研究,给出两种思路(一条是书上的,我会标出,另一条是自己想的):

注:本文中C m, n表示组合数上标为m,下标为n

一、先乘阶乘,再除以阶乘(《算法竞赛》P64)

例如:C m, n,这里m < n,所以C m, n = n(n-1)…(n-m+1)/m!

先算分子即乘以阶乘n(n-1)…(n-m+1),再除以所算的分母即除以阶乘,就是这种思路。

我们知道:C m, n = C n-m, n 所以在计算的时候就有两种情况,与C m, n和C n-m, n分别对应,这里有两种情况,列举如下:

1)利用C m, n = n(n-1)…(n-m+1)/m! ;

2)利用C n-m, n = n(n-1)…(m+1)/(n-m)! ;

为了使数字不溢出,我们首先考虑的是分子,在第一步乘以阶乘时,得到的数尽量要小,所以我们采取情况1)还是情况2),最先考虑的应该是分子的大小,即n(n-1)…(n-m+1)和n(n-1)…(m+1)的大小,你可以算一下,这里直接给出结论:

当 m > n - m时,n(n-1)…(m+1)要更小

当 m < n - m时,n(n-1)…(n-m+1)要更小

并不需要将两种都写出来,实际上,默认其中一种即可,比如默认用n(n-1)…(n+m)分子的处理方式,只需在前面加一个小判断 :if (m < n - m) m = n - m,这样即可保证m < n - m,便符合了默认情况,如选另外一种方式亦然

书中代码如下:

void CombiNumber(void)//求组合数
{
     
	int i, n, m;
	long long sum = 1;
    scanf("%d %d", &n, &m);
	if (m < n - m)  m = n - m;
	for (i = m + 1; i <= n; i ++)  sum *= i;
	for (i = 1; i <= n - m; i ++)  sum /= i;
	printf("sum = %lld\n", sum);
	
	return; 
}

二、乘除同算方法

想要做到乘除同时计算(一次循环两个步骤,乘、除),就要考虑C m, n,我们知道:C m, n = n(n-1)…(n-m+1)/m!,这意味着分子和分母都有有m项,经过一番研究,我发现:

对于上标m来说,想要使m被完全约分掉,对应的分子项为n-m+1,意思就是:想要完全约去一个数m,最好的情况n可以整除m,需要1项乘积即可,最坏的情况需要用n乘n-1乘n-2…一直到n-m+1项,需要m项乘积,所以,对于分母第一项m,无法找到统一的标准来作为其完全约去需要乘到的某一项,所以分子从n到n-1到…n-m+1依次相乘来约分m到m-1到…1是行不通的

但是分子从n到n-1到…n-m+1依次相乘来约分m到m-1到…1是行不通,不代表分母从1到2到…到m行不通,根据上述观点,分子第一项n除以分母第一项1,一定能整除,为n ; 分子前两项n(n-1)除以分母第二项2,一定能整除(因为n和n-1其中一个必为偶数);依次类推…

其实核心就一条,那就是:连续的m个数字相乘,一定能将m整除

式子可表示为:n(n-1)…(n-m+1) % m == 0

我的代码如下:

void CombiNumber(void)//求组合数
{
     
    int i, m, n;
	long long sum = 1;
	scanf("%d %d", &n, &m);
	for (i = 1; i <= m; i ++)
	{
     
		sum *= n --;
		sum /= i;
	}
	printf("sum = %lld\n", sum);
	 
	return;
}

但是这还不是最优的,因为这里分子子项从大(n)到小(n-m+1)依次相乘,对应约分的分母子项是从小(1)到大(m)的,这样会有溢出风险,不如让分子子项从小到大依次相乘,也满足了分子子项相乘后可整除对应分母子项的条件
优化代码如下:

void CombiNumber(void)//求组合数
{
     
    int i, m, n;
	long long sum = 1;
	scanf("%d %d", &n, &m);
	if (m > n - m)  m = n - m;
	n = n - m + 1;
	for (i = 1; i <= m; i ++)
	{
     
		sum *= n ++;
		sum /= i;
	}
	printf("sum = %lld\n", sum);
	 
	return; 
}

今天就分享到这里啦,谢谢您的观看!

你可能感兴趣的:(#,数据结构与习题,算法)