排列與排列數、組合與組合數
排列與排列數
-
從\(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)為例的耗時(再大遞推還是秒出,但遞歸我可能真的等不了了)