排列&&组合

        组合数学中的研究对象中,根据有无顺序,一般分为排列问题和组合问题,排列与组合的根本区别在于前者与元素的顺序有元,后者与元素的顺序无关。

计数问题的解决思路

  1. 只取要的。即把各种符合条件的情况都枚举出来,利用加法原理求和。
  2. 先全部取,再减去不要的。把所有的情况都枚举出来,然后减去不符合条件的的情况。
  3. 先取后排。 即先把各步中符合条件的组合或者排列都计算出来,再根据乘法原理求积。

排列

       排列(permutation),数学的重要概念之一。有限集的子集按某种条件的序化法排成列、排成一圈、不许重复或许重复等。从n个不同元素中每次取出m(1≤m≤n)个不同元素,排成一列,称为从n个元素中取出m个元素的无重复排列或直线排列,简称排列。从n个不同元素中取出m个不同元素的所有不同排列的个数称为排列种数或称排列数,记为  (或  ),

//定义来自百度

1.选排列

从n个元素的集合S中,有序选取处r个元素(r<=n)叫做S的一个r排列,不同的排列总数记作P(n,r))或者P_n^r

不同排列的定义:

含有的元素不完全相同,所含元素相同但是顺序不同

选排列方案数:

读作:n的降r阶乘 

  1. 从n个不同元素中可以重复的选取除m个元素的排列,叫做相异元素可重复排列。其排列方案数为n^m
  2. 如果在n个元素中有n_1个元素彼此相同,有n_2个元素彼此相同,……,又有n_m个元素彼此相同,并且n_1+n_2+...+n_m=n,则这n个元素的全排列叫做不全相异元素的全排列。计算公式为  \frac{n!}{n_1!*n_2!*...*n_m!}
  3. 如果n_1+n_2+...+n_m=r,则是从n个元素中选区r个元素(r<n)的选排列叫做不全相异元素的选排列,其排列计算公式为\frac{P(n,r)}{n_1!*n_2!*...*n_m!}

例题:1-n个数中选r个的排列组合类型

This is the code

#include<iostream>
#include<cstdio>
using namespace std;
int a[101];
bool v[101];
void dfs(int n,int m, int k)
{
    if(k==m+1)//表示已经填充了m个数,输出
    {
        for(int i=1; i<m; ++i)//输出
            printf("%d ", a[i]);
        printf("%d\n", a[m]);
        return ;//回溯下一个
    }
    for(int i=k;i<=n;++i)
    {
        if(!v[i])//判断这个数是否在这一次的序列中出现过,没有出现为false
        {
            v[i]=true;//标记
            a[k]=i;//填充数字
            dfs(n,m,k+1);//递归查找
            v[i]=false;//删除标记,查找下一种情况
        }
    }
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    dfs(n,m,1);
    return 0;
}

2.全排列

P(n,n)时求得就是全排列,计算全排列的方式

//注意看代码中的库函数计算全排列

This is the code

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int a[101];
void Perm(int n,int k)//手写全排列,dfs思想
{
    if(k==n)
    {
        for(int i=0;i<n-1;++i)
            printf("%d ",a[i]);
        printf("%d\n",a[n-1]);
        return ;
    }
    for(int i=k;i<n;++i)
    {
        swap(a[k],a[i]);
        Perm(n,k+1);
        swap(a[k],a[i]);
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    /*
    for(int i=0;i<n;++i)
        scanf("%d",&a[i]);
    sort(a,a+n);
    Perm(n,0);
    
    */
    C++ 中,头文件<algorithm> 里的 next_permutation() 函数,可产生字典序的全排列
    如下,给出从一组全排列的情况,使用next_permutation()函数可以生成下一种的全排列的情况
    因此一般先使用sort()进行排序,即可生成所有全排列的情况
    for(int i=0;i<n;++i)
        a[i]=i+1;
    sort(a,a+n);
    int k=0;
    do
    {
        ++k;
        for(int i=0;i<n-1;++i)
            printf("%d",a[i]);
        printf("%d ",a[n-1]);
        if(k%10==0)
            printf("\n");

    }while(next_permutation(a,a+n));
    return 0;
}

3.错位排列

设 (a_1,a_2,...,a_n)是 \{1,2,...,n\} 的一个全排列,若对任意的 i \in \{1,2,...,n\} 都有 a_i\neq i ,则称 (a_1,a_2,...,a_n) 是 \{1,2,...,n\} 的错位排列。

用 D_n 表示 \{1,2,...,n\} 的错位排列的个数,有:D_n=n!*(1-1/1!+1/2!-1/3!+...+(-1^n)/n!)

//可以使用容斥定理证明

4.圆排列

从 n  个不同元素中选取 r 个元素,不分首尾地围成一个圆圈的排列叫做圆排列,其排列方案数为:P(n,r)/r

当 r=n 时,则为圆排列的全排列,其排列方案数为:n!/n=(n-1)!

组合

从 n 个元素的集合 S 中,无序的选出 r 个元素,叫做 S 的一个 r 组合。所有不同组合的个数,叫做组合数,记作: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

组合数公式

\\ C(n,r)=C(n,n-r) \\ \\ C(n,r) =C(n-1,r)+C(n-1,r-1) \\ \\ C(n,0)+C(n,1)+......+C(n,n-1)+C(n,n)=2^n  杨辉三角的应用和二项式定理

可重复组合:

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

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

 

例题:球迷购票问题

组合数计算方法

1. 暴力计算

C(n,k)=\frac{n*(n-1)*(n-2)*....*(n-k+1)}{k*(k-1)*...*2*1}

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
ll multi(ll a,ll b)//快速乘
{
    ll ans=0;
    while(b)
    {
        if(b&1)
        {
            ans+=a;
            b--;
        }
        a=(a<<=1);
        b>>=1;
    }
    return ans;
}
ll C(int n,int k)//暴力计算
{
    int tot=max(k,n-k);
    if(tot==k)
        k=n-k;
    ll sum1=1,sum2=1;
    for(int i=tot+1;i<=n;++i)
        sum1=multi(sum1,i);
    for(int i=1;i<=k;++i)
        sum2=multi(sum2,i);
    return sum1/sum2;
}
int main()
{
    int n,k;
    cin>>n>>k;
    k=min(k,n-k);
    cout<<C(n,k);
    return 0;
}

2.杨辉三角方法递推计算

C(n,r) =C(n-1,r)+C(n-1,r-1)

#include<iostream>
#include<cstdio>
using namespace std;
int c[10][10];
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    c[0][0]=0;
    for(int i=1;i<=n;++i)
    {
        for(int j=0;j<=i;++j)
            if(!j||i==j)
                c[i][j]=1;
            else
                c[i][j]=c[i-1][j]+c[i-1][j-1];
    }
    printf("%d",c[n][k]);
}

3.重数--约分

约分之后,分母即会变为 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] 中所存储的即为约分后质数因子的重数,再利用高精度加法,将答案存储,最后倒序输出即可。

例题: 

题目描述

求出C(n,r)的最后十位,其中0<r≤n≤30000,输出时不足十位数也按十位输出,此时高位用0表示。C(n,r)=n×(n-1)×……×(n-r+1)/(1×2×3×……×r)。

输入数据为两个以空格隔开的自然数n,r。

输入

一行两个整数

输出

一行,10位数字

样例输入

5 2

样例输出

0000000010

题目分析:使用重数计算求解,并带有部分高精度运算知识

This is the code

#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
using namespace std;
bool temp[30001];
vector<int > prime,fac;
int ret[10];//储存后十位数
void Prime()//得到素数
{
    for(int i=2;i<=30000;++i)
    {
        if(!temp[i])
        {
            prime.push_back(i);//储存素数
            fac.push_back(0);//储存这个素数的重数
        }
        for(int j=i*i;j<=30000;j+=i)
            temp[j]=true;
    }
}
void add(int x,int t)//
{
    for(int i=0;i<prime.size()&&prime[i]<=x;++i)
    {
        while(!(x%prime[i]))
        {
            x/=prime[i];
            fac[i]+=t;
        }
    }
}
int main()
{
    Prime();
    int n,r;
    scanf("%d%d",&n,&r);
    if(r>n-r)//简化运算
        r=n-r;
    for(int i=0;i<r;++i)
    {
        add(n-i,1);//累加n-r+1--n的因子
        add(i+1,-1);//减去1-r的因子
    }
    memset(ret,0,sizeof(0));
    ret[0]=1;
    for(int i=0;i<prime.size();++i)
    {
        for(int j=0;j<fac[i];++j)
        {
            for(int k=0;k<10;++k)
                ret[k]*=prime[i];
            for(int k=0;k<10;++k)//高精度运算
            {
                if(k<9)
                    ret[k+1]+=ret[k]/10;
                ret[k]%=10;
            }
        }
    }
    for(int i=9;i>=0;--i)
        printf("%d",ret[i]);
    return 0;

}

测试数据

#1:

输入 29999 27381

输出 4531330240

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值