组合数

Posted kimsimple

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了组合数相关的知识,希望对你有一定的参考价值。

从m个不同元素中,任取n(n≤m)个元素并成一组,叫做从m个不同元素中取出n个元素的一个组合

从m个不同元素中取出n(n≤m)个元素的所有组合的个数,叫做从m个不同元素中取出n个元素的组合数

 combinatorial number

  /     

 

 

在线性写法中被写作C(m,n)。

c(m,n)=p(m,n)/n!=m!/((m-n)!*n!)

 组合数的计算:C(n,k)有多种解法,1,dp递推;2,直接计算;3,lucas定理

 

性质:

性质1 C(n,m)= C(n,n-m)互补性质

  例如C(9,2)=C(9,7),即从9个元素里选择2个元素的方法与从9个元素里选择7个元素的方法是相等的。

  规定:C(m,0)=1

性质2 C(n,m)=  C(n-1,m-1)+C(n-1,m) 组合恒等式

摘自http://blog.csdn.net/wty__/article/details/20048467

1.最简单的情况,数据比较小,直接采用C(a, b) = a * (a - 1) *....* (a - b + 1) / (b * (b - 1) *...* 2 * 1)
试用数据范围:a <= 29。在a = 30, b = 15时,计算分子
乘积时即超范围

补救1.是将先乘后除改为交叉地进行乘除,先除能整除的,但也只能满足n稍微增大的情况,n最多只能满足两位数。

补救2.是换用高精度运算,这样结果不会有问题,只是需要实现大数相乘、相除和取模等运算,实现起来比较麻烦,时间复杂度为O(n)。

 1 LL combi(LL a,LL b)///从a中取b
 2 {
 3     if(a<b){
 4         return 0;
 5     }
 6     LL r=1;
 7     for(int i=a;i>=a-b+1;i--)
 8         r*=i;
 9     for(int j=b;j>1;j--)
10         r/=j;
11     return r;
12 }

2.预处理(打表)出需要的组合数,如需计算较大的组合数可采用(经常会取模,也很方便)。使用C(a, b) = C(a - 1, b - 1) + C(a - 1, b - 1)递推处理
因为计算过程中采用递推的加法运算,所以不取模的时候最大可以算到a = 66
但是,这种情况一般伴随着取模的操作,所以考虑到内存限制的时候,一般可以计算到a = 1000(不一定,受限于内存)

生成的复杂度为O(n^2),查询复杂度为O(1)。

算法的预处理时间较长,另外空间花费较大,都是平方级的,优点是实现简单,查询时间快。

 1 const int MAXN1 = 1000;  
 2 const int MAXN2 = 1000;  
 3 LL f[MAXN1][MAXN2];  
 4   
 5 void init()  
 6 {  
 7     FF(i, 0, MAXN1)  
 8         f[i][0] = 1;  
 9     FF(i, 1, MAXN1)  
10     {  
11         FE(j, 1, min(i, MAXN2 - 1))  
12             f[i][j] = (f[i - 1][j] + f[i - 1][j - 1]) % MOD;  
13     }  
14 }  

 

3.质因数分解:

采用分解质因子的方式,可以计算足够大的数(因为数字会超过long long的范围,所以结果依然用质因子表示,模板中计算出了相应的数)

设n!分解因式后,质因数p的次数为a;对应地m!分解后p的次数为b;(n-m)!分解后p的次数为c;则C(n,m)分解后,p的次数为a-b-c。计算出所有质因子的次数,它们的积即为答案,即C(n,m)=p1 a1-b1-c1p2 a2-b2-c2…pk ak-bk-ck。n!分解后p的次数为:n/p+n/p 2+…+n/p k。算法的时间复杂度比前两种方案都低,基本上跟n以内的素数个数呈线性关系,而素数个数通常比n都小几个数量级,例如100万以内的素数不到8万个。用筛法生成素数的时间接近线性。该方案1秒钟能计算 1kw数量级的组合数。如果要计算更大,内存和时间消耗都比较大。https://segmentfault.com/a/1190000005072018#articleHeader0

 1 #include <iostream>
 2 #include "cstdio"
 3 #include "map"
 4 #include "cmath"
 5 using namespace std;
 6 #define LL long long
 7 map <int, LL> m;
 8 
 9 ///分解质因数
10 ///k为1或-1
11 void fun(int n, int k)
12 {
13     for (int i = 2; i <= sqrt(n * 1.0); i++)
14     {
15         while (n % i == 0)
16         {
17             n /= i;
18             m[i] += k;///存储因数+次数(k=-1倒数)
19         }
20     }
21     if (n > 1)
22     {
23         m[n] += k;
24     }
25 }
26 
27 ///快速幂
28 LL quick_pow(LL a, LL b)
29 {
30 
31     LL ret = 1;
32     while (b)
33     {
34         if (b & 1)
35         {
36             ret *= a;
37         }
38         b >>= 1;
39         a *= a;
40     }
41     return ret;
42 }
43 
44 ///求组合数
45 LL C(LL a, LL b)
46 {
47     if (a < b || a < 0 || b < 0)
48         return 0;
49     m.clear();
50     LL ret = 1;
51     b = min(a - b, b);
52     for (int i = 0; i < b; i++)
53     {
54         fun(a - i, 1);///分母
55     }
56     for (int i = b; i >= 1; i--)
57     {
58         fun(i, -1);///分子
59     }
60 
61     ///以下计算出了具体的数
62     for (__typeof(m.begin()) it = m.begin(); it != m.end(); it++)
63     {
64         if ((*it).second != 0)
65         {
66             ret *= quick_pow((*it).first, (*it).second);///快速幂,在之前分解质因数时 在过程+(-1)中其实已经运算了一部分,所以才不容易乘过界。
67         }
68     }
69     return ret;
70 }
71 int main()
72 {
73     LL a,b;
74     while(~scanf("%lld%lld",&a,&b)&&a&&b)
75     {
76         cout<<C(a,b)<<endl;
77     }
78 }

 

Lucas定理,设p是一个素数(题目中要求取模的数也是素数),将n,m均转化为p进制数,表示如下:

满足下式:

即C(n,m)模p等于p进制数上各位的C(ni,mi)模p的乘积。利用该定理,可以将计算较大的C(n,m)转化成计算各个较小的C(ni,mi)。该方案能支持整型范围内所有数的组合数计算,甚至支持64位整数,注意中途溢出处理。该算法的时间复杂度跟n几乎不相关了,可以认为算法复杂度在常数和对数之间。

 1 #include <stdio.h>
 2 const int M = 10007;
 3 int ff[M+5];  //打表,记录n!,避免重复计算
 4 
 5 //求最大公因数
 6 int gcd(int a,int b)
 7 {
 8     if(b==0)
 9 return a;
10 else
11 return gcd(b,a%b);
12 }
13 
14 //解线性同余方程,扩展欧几里德定理
15 int x,y;
16 void Extended_gcd(int a,int b)
17 {
18     if(b==0)
19     {
20        x=1;
21        y=0;
22     }
23     else
24     {
25        Extended_gcd(b,a%b);
26        long t=x;
27        x=y;
28        y=t-(a/b)*y;
29     }
30 }
31 
32 //计算不大的C(n,m)
33 int C(int a,int b)
34 {
35     if(b>a)
36 return 0;
37     b=(ff[a-b]*ff[b])%M;
38     a=ff[a];
39     int c=gcd(a,b);
40     a/=c;
41     b/=c;
42     Extended_gcd(b,M);
43     x=(x+M)%M;
44     x=(x*a)%M;
45     return x;
46 }
47 
48 //Lucas定理
49 int Combination(int n, int m)
50 {
51         int ans=1;
52 int a,b;
53 while(m||n)
54 {
55         a=n%M;
56 b=m%M;
57 n/=M;
58 m/=M;
59 ans=(ans*C(a,b))%M;
60 }
61 return ans;
62 }
63 
64 int main(void)
65 {
66         int i,m,n;
67 ff[0]=1;
68 for(i=1;i<=M;i++)  //预计算n!
69 ff[i]=(ff[i-1]*i)%M;
70  
71 scanf("%d%d",&n, &m);
72 printf("%d\\n",func(n,m));
73  
74 return 0;
75 }

 

以上是关于组合数的主要内容,如果未能解决你的问题,请参考以下文章

如何组合绑定片段而不将它们包装在 XML 文字中

48个值得掌握的JavaScript代码片段(上)

精心收集的 48 个 JavaScript 代码片段,仅需 30 秒就可理解!(转载)

片段组合在 Relay 中是如何工作的?

如何将片段中的 ListView 对象的数据传递给 Activity?

为 memcached 和 Rails 组合片段和对象缓存的最佳方式