zoukankan      html  css  js  c++  java
  • 求组合数

    从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
    ",func(n,m));
    73  
    74 return 0;
    75 }
  • 相关阅读:
    Java项目中读取properties文件,以及六种获取路径的方法
    在eclipse中使用JUnit4,以及使用JUnit4进行单元测试的技巧
    [Evernote]印象笔记使用经验技巧
    使用Word2010发布博客文章
    Win7/8 绿色软件开机启动
    常见笔试题
    排序
    数据库知识归纳(索引)
    数据库知识归纳(事务)
    Redis
  • 原文地址:https://www.cnblogs.com/kimsimple/p/6390996.html
Copyright © 2011-2022 走看看