数学 —— 组合数学 —— 组合

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011815404/article/details/88763537

【概念】

1.组合:从 n 个元素的集合 S 中,无序的选出 r 个元素,叫做 S 的一个 r 组合。

如果两个组合中,至少有一个元素不同,它们就被认为是不同的组合。

2.组合数:所有不同组合的个数,叫做组合数,记作:C_n^r 或 C(n,r)

由于每一种组合都可以扩展到 r!种排列,而总排列为 P(n,r) ,所以组合数 C_n^r=P(n,r)/r!=n!/(r!*(n-r)!),r\leqslant n

特别的,C(n,0)=1

3.可重复组合:从 n 个不同的元素中,无序的选出 r 个元素组成一个组合,且允许这 r 个元素重复使用,则称这样的组合为可重复组合。

其组合数记为:H(n,r)=C(n+r-1,r)=(n+r-1)!/((n-1)!*r!)

例题:

① 一班有10名同学,二班有8名同学,现每个班级要选出2名学生参加一个座谈会,求有多少种选法?

根据组合数与乘法原理,共有:C(10,2)*C(8,2)=1260 种

② 某班有10名同学,有4名女同学,现要选出3名学生,其中至少有一名女同学,求有多少种选法?

根据组合数与加法原理,共有:C(4,1)*C(6,2)+C(4,2)*C(6,1)+C(4,3)*C(6,0)=60+36+4=100 种

【组合数常用公式】

1)C_n^r=C_n^{n-r}

2)C_n^r=C_{n-1}^r+C_{n-1}^{r-1}

3)C_n^0+C_n^1+...+C_n^{n-1}+C_n^n=2^{n}(二项式定理)

【求组合数的方法】

首先 C(n,r) 的值一定是自然数,因为连续 r 个自然数的积一定被 r! 整除,因此求 C(n,r) 的值关键在于如何避免做除法。

1.约分

约分之后,分母即会变为 1,借此将除法化为乘法,约分方法是计算 1 到 n 之间的任意一个质数在 C(n,r) 的重数。

具体做法是对分子分母上的每个数分解质因子,用一个数组 C[] 来记录重数,若分子上的数分解一个质因子 p,则 C[p]++,反之若分母上的数分解出质因子 p,则 C[p]--,最后将每个质因子按其重数连乘即可。

注:质数 p 在自然数 n 中的重数是指自然数 n 的质因数分解式质数 p 出现的次数。例如:72=2*2*2*3*3,质数2在72的重数是3,质数3在72的重数是2。

将公式化为 C(n,r)=n!/((n-r)!*r!),通过直接计算质数 p 在 n! 中的重数而得到数组 C[],质数 p 在 n! 的重数为:

 n \:div\:p+n\:div\:p^2+n\:div\:p^3+...

例如:n=1000,p=3时,有 1000 div 3+1000 div 9+1000 div 27+1000 div 81+1000 div 243+1000 div 729=333+111+37+12+4+1=498,因此 1000!能被3^498 整除,但不能被 3^499 整除。

此外,根据公式:n \:div \:p^{k+1}=n\:div \:p^k\:div \:p,可以递推的求出 p 在 n!中的重数,如上例有:333 div 3=111 ,111 div 3=37,37 div 3=12,12 div 3=4,4 div 3=1

程序实现时,先求出 1 到 n 间所有质数,再对每个质数求重数,从而计算从 n-r+1 到 n 的因子的重数与从 1 到 r 的因子的重数,前者减去后者,C[i] 中所存储的即为约分后质数因子的重数,再利用高精度加法,将答案存储,最后倒序输出即可。

#include<cstdio>
#include<cstring>
#include<vector>
const int N=30000;
vector<int> prime,C;
bool vis[N];
int res[10];
void Get_Prime()
{
    memset(vis,true,sizeof(vis));
    for(int i=2;i<=N;i++)
    {
        if(vis[i])
        {
            prime.push_back(i);//存储质数
            C.push_back(0);//当前质数的重数为0
            for(int j=i*i;j<=N;j+=i)//筛除所有以i为因子的数
                vis[j]=false;
        }
    }
}
void Add(int n,int p)//记录重数个数
{
    for(int i=0;i<prime.size()&&prime[i]<=n;i++)
    {
        while(!(n%prime[i]))
        {
            n/=prime[i];
            C[i]+=p;
        }
    }
}
int main()
{
    Get_Prime();//打表获取质数

    int n,r;
    scanf("%d%d",&n,&r);

    if(r>n-r)//根据公式C(n,r)=C(n,n-r)简化计算
        r=n-r;

    for(int i=0;i<r;i++)
    {
        Add(n-i,1);//将n-r+1到n的因子加到C中去
        Add(i+1,-1);//将1到r的因子从C中减去
    }

    memset(res,0,sizeof(res));
    res[0]=1;
    for(int i=0;i<prime.size();i++)//枚举所有质数
    {
        for(int j=0;j<C[i];j++)//枚举对应质数的重数
        {
            for(int k=0;k<10;k++)
                res[k]*=prime[i];
            for(int k=0;k<10;k++)//高精存储答案
            {
                if(k<9)
                    res[k+1]+=res[k]/10;
                res[k]%=10;
            }
        }
    }

    for(int i=9;i>=0;i--)
        printf("%d",res[i]);
    printf("\n");

    return 0;
}

2.将 C(n,r) 的过程化为加法

利用公式 C_n^r=C_{n-1}^r+C_{n-1}^{r-1},将计算 C(n,r) 的过程化为加法来做,由于二项式展开系数的与杨辉三角一致,故该方法的实质就是求杨辉三角第 n 行,第 r 列上的数。

#include<cstdio>
const int N=25;
int f[N][N];
int main()
{
    f[0][0]=1;
    for(int i=1;i<=N-1;i++)
        for(int j=1;j<=i+1;j++)
            f[i][j]=f[i-1][j]+f[i-1][j-1];

    int n,r;
    scanf("%d%d",&n,&r);
    printf("%d\n",f[n+1][r+1]);

    return 0;
}

3.公式化简

C(n,m) = (n - m + 1) * C(n, m - 1) / m,由于除以 m,因此相对没那么容易越界

#define LL long long
LL calculate(LL n,LL m)//sum即为C(n,m)的值
{
    if(2*m>n)
        m=n-m;
    LL sum=1;
    for(LL i=1,j=n;i<=m;i++,j--)
        sum=sum*j/i;
    return sum;
}

猜你喜欢

转载自blog.csdn.net/u011815404/article/details/88763537