排列与排列数、组合与组合数
排列与排列数
-
从\(n\)个不同元素中,任取\(m(m \le n)\)个元素,按照一定的顺序排成一列,叫做从\(n\)个不同元素中取出\(m\)个元素的一种排列。
- 注意:排列的元素完全相同,顺序也完全相同
-
公式:\(P^m_n=\frac{n!}{(n-m)!}\)
-
如何理解这个公式:
-
例1:三名同学,成绩前两名有多少种可能性?
- 第一名:3种可能性
- 第二名:(3-1)种可能性
- 根据乘法原理,共有\(3*(3-1)\)种可能性
-
例2:七名同学,成绩前五名有多少种可能性?
- 由例一,易知有\(7*(7-1)*(7-2)*...*(7-5+1)\)
-
由例1例2进行进一步推导,用\(m,n\)代替具体的数字,可得
- \(n(n-1)(n-2)(n-3)...(n-m+1)\)
-
整理可得,
\(n(n-1)(n-2)(n-3)...(n-m+1)=\frac{1*2*3*...(n-2)(n-1)n}{1*2*3*...(n-m-1)(n-m)}\)
(即乘上\(\frac{1*2*3*...(n-m-1)(n-m)}{1*2*3*...(n-m-1)(n-m)}\),目的是得到完整的、从1开始的可以化为阶乘形式的数)
-
继续整理,得到\(P^m_n=\frac{n!}{(n-m)!}\)
-
但是通过第3步得到的式子,其实是不需要阶乘的
从大的数字开始往小乘,乘小的数字那么多个
-
-
组合与组合数
-
从\(n\)个不同元素中,任取\(m(m \le n)\)个元素并成一组,叫做从\(n\)个不同元素中取出\(m\)个元素的一个组合。
-
记号:\(C(n,k)\)或\(\binom{n}{k}\)
-
公式:\(\binom{n}{k}=\frac{P(n,k)}{P(k,k)}\)
-
推导公式:\(\binom{n}{k}=\frac{\frac{k!}{(k-n)!}}{\frac{n!}{(n-n)!}}=\frac{k!}{n!(k-n)!}\)
-
用更常用的\(m,n\)来表示组合数公式,就得到了\(\binom{m}{n}=\frac{n!}{m!(n-m)!}\)
-
理解公式:
-
观察可知,其实组合数就是在排列数公式上除以一个\(m!\)(\(P(k,k)\)其实就等于\(k!\))
-
思考一下:组合数与排列数的区别是组合数不要求顺序相同,所以如果按照排列数去求组合数,会出现一定数目的重复
-
打表找规律():
-
四选三(即m=3,n=4):
-
排列数:共\(P^3_4=\frac{4!}{(4-3)!}=\frac{4*3*2*1}{1}=24\)种
ABC,ABD,ACB,ACD,ADB,ADC,
BAC,BAD,BCA,BCD,BDA,BDC,
CAB,CAD,CBA,CBD,CDA,CDB,
DAB,DAC,DBA,DBC,DCA,DCB; -
观察,剔除重复项
ABC,ABD,ACD,BCD \(4\)种
-
找规律:保留了\(\frac{24}{6}\)种,即保留了\(\frac{4*3*2*1}{3*2*1}\)种,即\(C^m_n=\frac{P(m,n)}{m!}\)
-
嗯现在就假装推完了吧()
-
从大的数字开始往小乘,乘“小的数字那么多”个,再除以“小的数字开始往小乘,乘小的数字那么多个”
-
-
拓展公式
-
\(C^m_n=C^{n-m}_n\)
其实就是反选,从五个里面取出四个的组合数就相当于从五个里面取一个(取哪四个等同于不取哪一个)
-
\(C^{m-1}_n+C^m_n=C^m_{n+1}\)
这个似乎在\(m\)和\(n\)比较大的时候可以有效避免爆空间
-
\(C^0_n+C^1_n+......+C^n_n=2^n\)
-
\(C^r_r+C^r_{r+1}+......+C^r_n=C^{r+1}_{n+1}\)
-
\(\displaystyle\sum_{i=0}^kC^i_nC^{k-i}_{m}=C^k_{m+n}\)
这三个公式还没悟等悟了在补充
代码实现
-
定义式
int C(int m,int n){ //这里用longlong是因为第6行乘完之后极有可能暂时超过了int能表示的范围 long long result = 1; for(int i = 0;i<n;i++){ result *= (m-i); } for(int i = 2;i<=n;i++){ result /= i; } return (int)result; }
非常容易想到
-
递归版组合数求法
int C(int m,int n){ if(m == n) return 1; if(n == 1) return m; return C(m-1,n)+C(m-1,n-1); }
根据拓展公式(2)写的,优点是非常好写(手和脑子只能懒一个),缺点是慢......
这张图是以C(7,3)为例画的图(不得不说!这网站!实在是太强了)
一眼就能看见相当多的重复运算
-
递推版组合数求法
int C(int m,int n){ int c[n+1][m+1]; for(int i = 1;i<=m;i++){ c[1][i] = i; } for(int i = 2;i<=n;i++){ c[i][i] = 1; } for(int i = 2;i<n;i++){ for(int j = i+1;j<m;j++){ c[i][j] = c[i-1][j-1] + c[i][j-1]; } } for(int i = n+1;i<=m;i++){ c[n][i] = c[n-1][i-1] + c[n][i-1]; } return c[n][m]; }
既然\(C(m,n)\)与\(m,n\)都有关系,那么不妨开一个n行m列的二维数组(m行n列也行,自己调整一下)
看上图的所有的叶子节点,都是\(m=n\)或者\(n=1\)
\(m=n\)时\(C(m,n)=1\)
\(n=1\)时\(C(m,n)=m\)
因此,代码的第3~8行开出了如下表的一个二维数组
1 2 3 4 5 6 7 1 1 2 3 4 5 6 7 2 1 3 1 接着,根据拓展公式二,我们可以知道这个二维数组的每一项等于其左侧值和左上值的和
并且第m列(即上表中的第7列)除了最后一行都没有必要填上(填上也行)
所以代码的第9~13行将二维数组填充到了如下表所示的程度
1 2 3 4 5 6 7 1 1 2 3 4 5 6 7 2 1 3 6 10 15 3 1 然后第14~17行填完了最后一行
最后返回值即可
这是以C(36,24)为例的耗时(再大递推还是秒出,但递归我可能真的等不了了)