一、问题由来
昨晚群里一网友发表一组合问题,一开始以为他要从1~160正整数中取9个出来,这样的组合有多少种。他一问有这么多组合吗?重新看了一遍问题,又以为是把1~160分成9组,这样的组合有多少种。 (⊙﹏⊙)b 后跟他交流后才明白他的意思。
用通俗一点的语言重新表达下问题:从1~160正整数中取9个数(数值可一样),分别给9个人,使9个人的数值总和为160。请问这样的组合有多少个?
二、个人分析
第一反应,数据那么大,肯定是写个程序去处理。
首先排除153以上的数字,使用遍历法,把1~152从小到大依次给前8个人。如果前8个人的数值总和sum小于160,第9个人的数值肯定是160-sum,这样的组合便为一种组合。
在写程序的时候,使用的是递归算法。为了加快处理,每个人提取数字时的循环遍历最大值imax做了一个限制。因为每个人都会提取一个数字,最小值为1,所以第x个人的imax值等于160减去前x-1个数值总和,再减去后面剩余人的个数,即imax = 160 – sum(x-1) – (9 – x)。这样就不用再去遍历总和超过160的数字了(可以看出,这些组合数比我们要算的大的多)。下面是用的C语言写的,下标是从0开始。
#include <stdio.h>
#include <stdlib.h>
#define MAX_VALUE 160
#define MAX_GROUP 9
double count = 0;
int a[MAX_GROUP];
void ResultDisplay()
{
int i;
printf("%12.0f: ", count);
for(i=0;i<MAX_GROUP;i++)
printf("%3d ",a[i]);
printf("\n");
}
int Combination(int sum, int n)
{
int i=1;
if( sum>=MAX_VALUE)
return -1;
if(n == MAX_GROUP - 1){
a[n] = MAX_VALUE - sum;
count++;
ResultDisplay();
return 0;
}
for(i=1; i<=MAX_VALUE - sum - (MAX_GROUP - 1 - n);i++){
a[n] = i;
Combination(sum+i, n+1);
}
}
int main(int argc, char **argv)
{
Combination(0,0);
system("pause");
return (0);
}
用我的本本运行了一分钟,结果如下:
组合数已达到3万多,但前5个人还是1。这里打印耗了很多时间,如果去掉打印,结果会快N倍。去掉后运行过半小时(运行起来后,本本的风扇一直处于高速转动的状态,担心古董受不鸟,所以运行几分就暂停一小会),通过监视看到组合数达到300多亿,但第1个人的数还是1,第2个人的数字也没超过2(忘了截图,印象中还是1)。看到这样真正的天文数字,没敢再继续运行下去了,但由此推断出组合数肯定超1000亿。
PS:昨晚在群里回复说组合数肯定超1000亿。一位群管理员表现出一副很不屑和被我忽悠的样子(“1000亿?别瞎我,我输读的少”)。一向认为群管理的话会比较稳重,再说这是一个技术群。你可以怀疑我说的,我们可以一起讨论验证,但你说这样讽刺和挑衅的话,对于我来说,最好的反驳就是拿出证据放群里给他看。在打印处加了个条件中断,从99999999990(差10就1000亿)开始打印。运行了近两个小时的时间,结果终于出来了,当时已经晚上1点了,立马编辑好文字,给他回复了过去。
1000亿,第1个人还是1,零头可能都还没达到呢。。。
以上为插曲,回归正题。此数学问题提出的时候,最长运行时间仅半小时,即300多亿。当时就想有没有一个公式,能直接用公式把组合数算出来。为了直观点,就在Excel里做了几个简单点的组合,看能否找到规律和思路。如:5分为2,5分为3,5分为4,8分为4。
一开始从后面开始找规律,最后一个不用看,就看最后两个,发现如果前面所有的数之和固定,那最后两个数的和也是固定的(这是废话),那组合数为最后两个数之和,再减1。这应该很好理解,假设最后两个数之和为y,那组合分别为(1, y-1), (2, y-2), … , (y-1,1),共y-1个。再倒数第三位数在递增的时候,y在递减。看上去很有规律,但仔细去找的时候却发现都是一些等差数列的叠加啊。
换个方向吧,从前面开始找找看。如上图,8分为4中,把第1人给不同数时的组合数给列出来了。把它的公式写出来后,发现仅适合8分为4这个组合方式,其他的都算不出正确答案。
这时,想到高中时统计学的统计方法(好像是统计学吧?),就是找到第n个与n-1个的关系。
【以为文本内容含很多博客编辑器无法显示的公式,都将使用图片】
#include <stdio.h>
#include <stdlib.h>
#define MAX_VALUE 100
#define MAX_GROUP 7
double KCombination(int m, int n)
{
double sum=0;
int i=0;
if(m<n){
printf("Error!\n");
return (-1);
}
if(m==n)
return (1);
if(n==2)
return (m-1);
if(n==1)
return (1);
for(i=m-1;i>=n-1;i--){
sum += KCombination(i,n-1);
}
return sum;
}
int main(int argc, char **argv)
{
printf("the number of the method of combination is: %.0f\n",KCombination(MAX_VALUE,MAX_GROUP));
system("pause");
return(0);
}