组合数学中的研究对象中,根据有无顺序,一般分为排列问题和组合问题,排列与组合的根本区别在于前者与元素的顺序有元,后者与元素的顺序无关。
计数问题的解决思路
- 只取要的。即把各种符合条件的情况都枚举出来,利用加法原理求和。
- 先全部取,再减去不要的。把所有的情况都枚举出来,然后减去不符合条件的的情况。
- 先取后排。 即先把各步中符合条件的组合或者排列都计算出来,再根据乘法原理求积。
排列
排列(permutation),数学的重要概念之一。有限集的子集按某种条件的序化法排成列、排成一圈、不许重复或许重复等。从n个不同元素中每次取出m(1≤m≤n)个不同元素,排成一列,称为从n个元素中取出m个元素的无重复排列或直线排列,简称排列。从n个不同元素中取出m个不同元素的所有不同排列的个数称为排列种数或称排列数,记为 (或 ),
//定义来自百度
1.选排列
从n个元素的集合S中,有序选取处r个元素(r<=n)叫做S的一个r排列,不同的排列总数记作或者
不同排列的定义:
含有的元素不完全相同,所含元素相同但是顺序不同
选排列方案数:
读作:n的降r阶乘
- 从n个不同元素中可以重复的选取除m个元素的排列,叫做相异元素可重复排列。其排列方案数为
- 如果在n个元素中有个元素彼此相同,有个元素彼此相同,……,又有个元素彼此相同,并且,则这n个元素的全排列叫做不全相异元素的全排列。计算公式为
- 如果,则是从n个元素中选区r个元素(r<n)的选排列叫做不全相异元素的选排列,其排列计算公式为
例题: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.全排列
当时求得就是全排列,计算全排列的方式
//注意看代码中的库函数计算全排列
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.错位排列
设 是 的一个全排列,若对任意的 都有 ,则称 是 的错位排列。
用 表示 的错位排列的个数,有:
//可以使用容斥定理证明
4.圆排列
从 n 个不同元素中选取 r 个元素,不分首尾地围成一个圆圈的排列叫做圆排列,其排列方案数为:
当 r=n 时,则为圆排列的全排列,其排列方案数为:
组合
从 n 个元素的集合 S 中,无序的选出 r 个元素,叫做 S 的一个 r 组合。所有不同组合的个数,叫做组合数,记作: 或
不同组合:两个组合中存在不同元素
由于每一种组合都可以扩展到 r!种排列,而总排列为 P(n,r) ,所以组合数
特别的
组合数公式
杨辉三角的应用和二项式定理
可重复组合:
从 n 个不同的元素中,无序的选出 r 个元素组成一个组合,且允许这 r 个元素重复使用,则称这样的组合为可重复组合。
其组合数记为:
例题:球迷购票问题
组合数计算方法
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.杨辉三角方法递推计算
#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。
将公式化为 ,通过直接计算质数 p 在 n! 中的重数而得到数组 C[],质数 p 在 n! 的重数为:
例如: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 整除。
此外,根据公式:,可以递推的求出 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