相關內容:組合入門題目選做(應用在這兒呢,可配合該文章閱讀 )
一、基礎內容
(這部分內容大家應該都會了,可以直接跳過)
1. 一些定義
加法原理:一般地,做一件事,完成它可以有 \(n\) 類方法,在第一類辦法中有 \(m_1\) 種不同的方法,在第二類辦法中有 \(m_2\) 種不同的方法,……,在第 \(n\) 類辦法中有 \(m_n\) 種不同的方法, 那么完成這件事共有:\(N=m_1+m_2+...+m_n\) 種不同的方法。
比如在一個問題中,路徑之間是平行的關系,你可以選擇任意一條路徑到達終點,這些路徑就是滿足加法原理的一個關系。
乘法原理:一般地,做一件事,完成它需要分成 \(n\) 個步驟,做完第一步有 \(m_1\) 種不同的方法,做完第二步有 \(m_2\) 種不同的方法,……,做完第 \(n\) 步有 \(m_n\) 種不同的方法。 那么,完成這件事共有:\(N=m_1\times m_2\times ...\times m_n\) 種不同的方法。
這個應該很好懂。注意區分“方法”與“步驟”。
排列:把 \(n\) 個不同元素重新排列,方案數為:\(n!\)
從 \(n\) 個不同元素中選出 \(m\) 個元素排成一列,產生的不同排列的數量為:
\(A_n^m=\frac{n!}{(n-m)!}=n\times (n-1)\times ...\times (n-m+1)\)(也可記作 \(P_n^m\))
組合:從 \(n\) 個不同元素中選出 \(m\) 個組成一個集合(不考慮順序)的方案數。
\(C_n^m=\frac{n!}{m!(n-m)!}=\frac{n\times (n-1)\times ...\times (n-m+1)}{m\times (m-1)\times ...\times 2\times 1}\)
\(C_n^m\) 也記作 \(\binom{n}{m}\)。
2. 一些理解
怎么理解 \(C_n^m=\frac{n!}{m!(n-m)!}\) 呢?一個長度為 \(n\) 的排列,選取其中的 \(m\) 個元素。首先,我們已經知道,把 \(n\) 個不同元素重新排列,有 \(n!\) 種方案。那么,我們用總的方案數去掉重復的方案數就是答案。假設我們已經確定了一個選出的集合為 \(S\)(\(|S|=m\)),並且這個集合對應排列的前 \(m\) 個元素(順序可以打亂)。對於序列的前 \(m\) 個位置(即集合 \(S\) 中的每個元素),有 \(m!\) 種排列方法。對於序列的后 \(n-m\) 個元素,有 \((n-m)!\) 種排列方法。兩者滿足乘法原理,則有 \(m!\cdot (n-m)!\) 種排列。所以如果確定了集合 \(S\),則對於這個集合 \(S\),有 \(m!\cdot (n-m)!\) 種方案。可以看成是,每 \(m!\cdot (n-m)!\) 種方案,才能確定一個集合 \(S\)。那么產生不同集合 \(S\) 的數量為 \(\frac{n!}{m!(n-m)!}\)。
既然知道了 \(C_n^m=\frac{n!}{m!(n-m)!}\),那么 \(A_n^m=\frac{n!}{(n-m)!}\) 也非常好理解了。對於 \(A_n^m\),從 \(n\) 個不同元素中選出的 \(m\) 個元素可以重新排列。選出的 \(m\) 個元素有 \(m!\) 種排列方法,那么 \(A_n^m=C_n^m\times m!=\frac{n!}{m!(n-m)!}\times m=\frac{n!}{(n-m)!}\)。
二、組合數的求法
1. 當數據范圍小:利用 \(C_n^m=\frac{n!}{m!(n-m)!}\) 直接暴力計算。\(O(n)\) 的計算 \(n!\)。
2. \(n,m\leq 1000\)。用遞推法(楊輝三角)。\(C_{i,j}=C_{i-1,j}+C_{i-1,j-1}\)。
c[0][0]=1; for(int i=1;i<=n;i++){ c[i][0]=1; for(int j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod; }
3. \(n,m\leq 10^5,10^6\),求 \(C_n^m \bmod p\)。
分別計算分子與分母,然后相除。除以 \(m!(n-m)!\) 可以看成是乘以 \(m!(n-m)! \bmod p\) 的逆元。
預處理出 \(i!\) 和 \(\frac{1}{i!}\)(\(i! \bmod p\) 的逆元)。
預處理 \(i!\):用 \(fac_i\) 表示 \(i!\)。\(fac_0=1\),對於 \(\forall i>0\),\(fac_i=fac_{i-1}\times i\)。
預處理 \(\frac{1}{i!}\):用 \(inv_i\) 表示 \(i! \pmod p\) 的逆元,則 \(inv_m=mul(fac_m,p-2,p)\)。先 \(O(log)\) 計算 \(inv_m\)。\(\frac{1}{i!}=\frac{1}{1\times 2\times ...\times i\times (i+1)}\times (i+1)=\frac{1}{(i+1)!}\times (i+1)\),那么 \(inv_i=inv_{i+1}\times (i+1)\)。
//快速冪: int mul(int x,int n,int mod){ int ans=mod!=1; for(x%=mod;n;n>>=1,x=x*x%mod) if(n&1) ans=ans*x%mod; return ans; } //對階乘數及其逆元進行預處理: void init(){ f[0]=g[0]=1; for(int i=1;i<=n;i++) f[i]=f[i-1]*i%mod; //f(i) 表示 i! ,即 fac(i) g[n]=mul(f[n],mod-2,mod); //求逆元 for(int i=n-1;i;i--) g[i]=g[i+1]*(i+1)%mod; //g(i) 表示 1/(i!) ,即 inv(i) } //之后就可以調用: int solve(int n,int m){ return f[n]*g[m]%mod*g[n-m]%mod; }
4. 若題目中 \(n\) 非常大,甚至沒法用數組存下,而 \(m\) 非常小,則可以直接計算。
\(C_n^m=\frac{n!}{m!(n-m)!}\) ,因為 \(m\) 非常小,所以 \(m!\) 可以很快被計算出來。
再考慮 \(\frac{n!}{(n-m)!}\)。\(\frac{n!}{(n-m)!}=\frac{n\times (n-1)\times ...\times (n-m+1)\times (n-m)\times (n-m-1)\times ...\times 2\times 1}{(n-m)\times (n-m-1)\times ...\times 2\times 1}=n\times (n-1)\times ...\times (n-m+1)\)。只有 \(m\) 項。也可以暴力計算。
三、Catalan 數
吹一波 Dls 寫的,比這里寫的不知道高到哪里去了。
1. 卡特蘭數的定義
給定 \(n\) 個 \(0\) 和 \(n\) 個 \(1\),它們按照某種順序排成長度為 \(2n\) 的序列,滿足任意前綴中 \(0\) 的個數都不少於 \(1\) 的個數的序列的數量為:
\(Cat_n=\frac{1}{n+1} C_{2n}^n=\frac{(2n)!}{(n+1)!n!}\)。
2. 證明
為什么 \(Cat_n=\frac{1}{n+1} C_{2n}^n\) 呢?
考慮取補集。
首先,有 \(n\) 個 \(0\),\(n\) 個 \(1\) 的序列的數量為 \(C_{2n}^n\)(\(2n\) 個位置中取 \(n\) 個位置為 \(1\))。
令 \(n\) 個 \(0\),\(n\) 個 \(1\) 隨意排成一個長度為 \(2n\) 的序列,若它不滿足任意前綴中 \(0\) 的個數都不少於 \(1\) 的個數,找到它的第一個不合法的位置(不合法指前綴中 \(0\) 的個數小於了 \(1\) 的個數)。顯然,第一個不合法位置上的數一定是 \(1\),並且在它的前面,\(0\) 的個數與 \(1\) 的個數相等。假設有 \(i\) 個。那么這個位置(即第一個不合法的位置)后面肯定有 \(n-i\) 個 \(0\),\(n-i-1\) 個 \(1\)。考慮把前 \(2\times i+1\) 位(即不合法位置以及它前面的數)翻轉(\(0\) 變為 \(1\),\(1\) 變為 \(0\))。翻轉后,第一個不合法位置上的數為 \(0\),它的前面依然是 \(i\) 個 \(0\),\(i\) 個 \(1\),它的后面還是 \(n-i\) 個 \(0\),\(n-i-1\) 個 \(1\)。則翻轉后,序列共有 \(n+1\) 個 \(0\),\(n-1\) 個 \(1\)。對於一個不合法的序列,它總是能夠翻轉得到一個有 \(n+1\) 個 \(0\),\(n-1\) 個 \(1\) 的序列。那么我們接下來要證明的就是,對於所有這樣的序列,總是能夠找到一個不合法的序列與它對應。
類似地,在翻轉后的序列中,找到第一個不滿足前綴 \(1\) 的個數大於等於前綴 \(0\) 的個數的數的位置。很容易發現,在翻轉后的序列中,總是能找到這樣一個不合法的位置(因為有 \(n+1\) 個 \(0\),\(n-1\) 個 \(1\))。將這第一個不合法位置以及它前面的數翻轉回去,可以得到一個有 \(n\) 個 \(0\),\(n\) 個 \(1\) 的序列,並且存在前綴 \(0\) 的個數大於前綴 \(1\) 的個數。
所以,不合法序列將不合法位置翻轉,可以和一個 \(n-1\) 個 \(0\),\(n+1\) 個 \(1\) 的序列一一對應。那么,求不合法序列的數量就可以轉化為求有 \(n+1\) 個 \(0\),\(n-1\) 個 \(1\) 的序列的數量。有 \(n+1\) 個 \(0\),\(n-1\) 個 \(1\) 的序列的數量顯然為 \(C_{2n}^{n-1}\)(\(2n\) 個位置中取 \(n-1\) 個位置為 \(1\))。
所以合法序列的答案為:\(C_{2n}^n-C_{2n}^{n-1}=\frac{(2n)!}{n!n!}-\frac{(2n)!}{(n-1)!(n+1)!}=\frac{(2n)!(n+1)}{n!(n+1)!}-\frac{(2n)!n}{n!(n+1)!}=\frac{(2n)!}{n!n!(n+1)}=\frac{1}{n+1}\times \frac{(2n)!}{n!n!}=\frac{1}{n+1} C_{2n}^n\)
四、錯排
考慮一個有 \(n\) 個元素的排列,若一個排列中所有的元素都不在自己原來的位置上,那么這樣的排列就稱為原排列的一個錯排。
\(D_n\) 表示 \(n\) 個元素的錯排方案數。
錯排公式:\(D_n=(n-1)\times (D_{n-1}+D_{n-2})\)(\(n>2\))
如何理解?考慮序列中 \(1\) 元素的位置。\(1\) 元素能放的位置為 \(2,3,...,n\),有 \(n-1\) 種放的位置。
假設 \(1\) 放在了 \(k\) 這個位置。接下來我們要關心的是 \(k\) 要放在什么位置。有兩種情況。
-
\(k\) 放在 \(1\) 的位置。相當於 \(1\) 與 \(k\) 交換了位置,對剩下的數不會產生影響(剩下的 \(n-2\) 個數依然保持一一對應關系),則剩下的 \(n-2\) 個數可以繼續進行錯排。所以這種情況的方案數為 \((n-1)\times D_{n-2}\)。
-
\(k\) 不放在 \(1\) 的位置。\(1\) 的位置確定了,剩下的數(除了 \(k\))依然有自己的對應關系。新增一個對應關系 \((1,k)\) 表示 \(k\) 不能放在 \(1\) 的位置。問題就轉化為了求 \(n-1\) 個數的錯排方案(想一想錯排的定義,\(k\) 與剩下的數每個數都不能在特定的位置上,其實就是錯排)。所以這種情況的錯排方案數為 \((n-1)\times D_{n-1}\)。
於是可以得到 \(D_n=(n-1)\times (D_{n-1}+D_{n-2})\)(\(n>2\))。
d[0]=1,d[1]=0,d[2]=1; for(int i=3;i<=1e6;i++) d[i]=(i-1)*(d[i-1]+d[i-2])%mod; //錯排
五、Lucas 定理
若 \(p\) 是質數,則對於任意整數 \(1\leq m\leq n\),有:
\(C_n^m\equiv C_{n \bmod p}^{m \bmod p}\times C_{n/p}^{m/p} \pmod p\)
也就是把 \(n\) 和 \(m\) 表示成 \(p\) 進制數,對 \(p\) 進制下的每一位分別計算組合數,最后再乘起來。
#include<bits/stdc++.h> #define int long long const int N=1e5+5; int t,n,m,p; int mul(int x,int n,int mod){ int ans=mod!=1; for(x%=mod;n;n>>=1,x=x*x%mod) if(n&1) ans=ans*x%mod; return ans; } int C(int n,int m){ if(m>n) return 0; int x=1,y=1; for(int i=n-m+1;i<=n;i++) x=x*i%p; for(int i=2;i<=m;i++) y=y*i%p; return x*mul(y,p-2,p)%p; } int lucas(int n,int m){ if(!m) return 1; return C(n%p,m%p)*lucas(n/p,m/p)%p; } signed main(){ scanf("%lld",&t); while(t--){ scanf("%lld%lld%lld",&n,&m,&p); printf("%lld\n",lucas(n,m)); } return 0; }
若 \(p\) 不是質數,可以用擴展 Lucas 定理。在這里就不講了,推薦閱讀:擴展盧卡斯詳解。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e6+5; int n,m,p,cnt,a[N],b[N]; int mul(int x,int n,int mod){ int ans=mod!=1; for(x%=mod;n;n>>=1,x=x*x%mod) if(n&1) ans=ans*x%mod; return ans; } int exgcd(int a,int b,int &x,int &y){ if(!b) return x=1,y=0,a; int d=exgcd(b,a%b,x,y); int z=x; x=y,y=z-y*(a/b); return d; } int inv(int n,int mod){ int x,y; exgcd(n,mod,x,y); return (x%mod+mod)%mod; } int fac(int n,int p,int k){ //k=p^x if(!n) return 1; int ans=1; for(int i=2;i<=k;i++) if(i%p!=0) ans=ans*i%k; ans=mul(ans,n/k,k); for(int i=2;i<=n%k;i++) if(i%p!=0) ans=ans*i%k; return ans*fac(n/p,p,k)%k; } int C(int n,int m,int p,int k){ if(n<m) return 0; int a=fac(n,p,k),b=fac(m,p,k),c=fac(n-m,p,k),cnt=0; for(int i=p;i<=n;i*=p) cnt+=n/i; for(int i=p;i<=m;i*=p) cnt-=m/i; for(int i=p;i<=n-m;i*=p) cnt-=(n-m)/i; return a*inv(b,k)%k*inv(c,k)%k*mul(p,cnt,k)%k; } int CRT(int m){ int ans=0; for(int i=1;i<=cnt;i++){ int M=m/b[i],t=inv(M,b[i]); ans=(ans+a[i]%m*M%m*t%m)%m; } return ans; } int exLucas(int n,int m,int p){ int t=p; for(int i=2;i*i<=p;i++){ int now=1; if(t%i==0){ b[++cnt]=i; while(t%i==0) t=t/i,now*=i; a[cnt]=C(n,m,i,now),b[cnt]=now; } } if(t!=1) b[++cnt]=t,a[cnt]=C(n,m,t,t); return CRT(p); } signed main(){ scanf("%lld%lld%lld",&n,&m,&p); printf("%lld\n",exLucas(n,m,p)); return 0; }
六、容斥原理
\(\left|\bigcup\limits_{i=1}^{n}A_i\right|=\sum\limits_{i=1}^n\left| A_i \right|-\sum\limits_{1\leq i<j\leq n}\left|A_i\cap A_j\right|+\sum\limits_{1\leq i<j<k\leq n}\left|A_i\cap A_j\cap A_k\right|-\cdots+(-1)^n\left| A_1\cap \cdots \cap A_n\right|\)
\(\left|\bigcup\limits_{i=1}^{n}A_i\right|=\sum\limits_{k=1}^n (-1)^{k+1}(\sum\limits_{1\leq i_1<\cdot<i_k\leq n}\left|A_{i_1}\cap\cdots\cap A_{i_k}\right|)\)
\(\left|\bigcup\limits_{i=1}^{n}A_i\right|=\sum\limits_{\emptyset\neq J\subseteq\{1,2,...,n\}} (-1)^{\left|J\right|-1}\left|\bigcap\limits_{j\in J}A_j\right|\)
常見應用:題中給出 \(n\) 個需要滿足的條件。
使用容斥,枚舉不被滿足的條件集合 \(S\) 和對應的方案數,其系數為 \((-1)^{集合大小}\)。
具體地,設 \(A\) 為條件集合。式子:
\(\sum\limits_{S\in A}(-1)^{\left| S\right|}\cdot f(S中的條件不被滿足)\)
七、第二類斯特林數
把 \(n\) 個不同的球放到 \(r\) 個相同的盒子里,假設沒有空盒,則放球方案數記做 \(S(n,r)\) 或 \(\begin{Bmatrix}n\\r\end{Bmatrix}\),稱為第二類 \(\text{Stirling}\) 數。
1. 求法
有一個顯然的遞推式:
\(S(n,r)=rS(n-1,r)+S(n-1,r-1),n>r\geq 1\)
即討論當前的球是放入以前的盒子還是放入一個新盒子里。如果要放入以前的盒子,那么把這個球放入任意一個盒子,這個盒子就相當於與其他的盒子不同,所以還要乘以 \(r\)。
同樣有通項:
\(\begin{Bmatrix}n\\k\end{Bmatrix}=\dfrac{1}{k!}\displaystyle\sum\limits_{i=0}^k (-1)^i \dbinom{k}{i} (k-i)^n\)
注意到“盒子非空”可以作為容斥的一個條件,\(k\) 個盒子就對應 \(k\) 個條件。枚舉空盒的個數 \(i\),\(k\) 個盒子選 \(i\) 個盒子是空盒的方案數為 \(C_k^i\)。確定了有 \(i\) 盒子是空盒,那么剩下 \(k-i\) 個盒子是可以放球的,則每個球都有 \(k-i\) 個選擇方式(可以放到 \(k-i\) 個盒子中的任意一個里),所以要乘上 \((k-i)^n\)。由於盒子是相同的最后要除以 \(k!\)。
2. 性質
第二類 \(\text{Stirling}\) 數的性質:
\(x^n=\displaystyle\sum\limits_{k=0}^n\begin{Bmatrix}n\\k\end{Bmatrix} x^{\underline k}\)
其中 \(x^{\underline k}\) 表示 \(x\) 的 \(k\) 次下降冪,\(x^{\underline k}=\frac{x!}{(x-k)!}\)。
怎么理解呢?\(x^n\) 即 \(n\) 個球放入 \(x\) 個不相同的盒子的方案數(所以也可以有空盒)。枚舉非空盒子的個數 \(k\),\(x\) 個盒子中選 \(k\) 個盒子是非空的,方案數為 \(C_x^k\)。乘上 \(n\) 個不同的球放到 \(k\) 個相同的盒子里的方案數 \(S(n,k)\)。除此之外,因為盒子是區分的,所以還要乘以 \(k!\)。於是可以得到 \(x^n=\sum\limits_{k=0}^n S(n,k) \cdot C_x^k \cdot k!\)。
又因為 \(x^{\underline k}=\frac{x!}{(x-k)!}\),於是就可以得到上面那個式子。
其實這個是可以 推出來 的。
補充:有三種說法, \(k\) 的上界分別是 \(n,x,\min(n,x)\)。因為 \(k>x\) 或 \(k>n\) 的時候對答案的貢獻為 \(0\),所以三種都行。
關於下降冪的一個性質:
\(\displaystyle\binom{n}{i} i^{\underline j}=\binom{n-j}{i-j} n^{\underline j}\)
直接展開就行了。
八、圖的計數
以下 \(n\) 均表示節點個數。
01 帶編號無向圖
求:帶編號無向圖的個數。(帶編號就是說,點與點之間是區分的)
圖與圖之間不同,當且僅當它們之間有邊不同。
考慮每一條邊是否選。
因為有 \(n\) 個節點,所以無向邊的數量為 \(\tbinom{n}{2}\)。每條邊可以選或不選,那么就有 \(2^{\tbinom{n}{2}}\) 種方案。
則 \(n\) 個節點的帶編號無向圖的數量為 \(2^{\tbinom{n}{2}}\)。
02 帶編號無向連通圖
令 \(g(n)\) 表示 \(n\) 個點的無向圖個數(由上可知 \(g(n)=2^{\tbinom n 2}\)),\(f(n)\) 表示 \(n\) 個點的無向連通圖個數。
枚舉普通無向圖中 \(1\) 號點所在連通塊的大小。嘗試用 \(f\) 來表示 \(g\)。
具體來說,我們從 \(1\) 到 \(n\) 枚舉 \(1\) 號點所在連通塊的大小 \(i\)。目前只確定了這個連通塊中有 \(1\) 號點, 那么剩下的 \(i-1\) 個點要從剩下的 \(n-1\) 個點中選(從剩下的 \(n-1\) 個點中選與 \(1\) 號點在同一個連通塊的 \(i-1\) 個點),方案數為 \(\dbinom{n-1}{i-1}\)。點集已經確定,\(i\) 個點的連通塊的構成方式有 \(f_i\) 種。除此之外,剩下的點沒有任何限制,所以選擇連通塊以外的點的構成方式有 \(g(n-i)\) 種。則:
\(g(n)=\displaystyle\sum\limits_{i=1}^n \binom{n-1}{i-1} \cdot f(i) \cdot g(n-i)\)
於是我們可以根據這個式子算出 \(f(n)\),即:
\(f(n)=\displaystyle g(n)-\sum\limits_{i=1}^{n-1} \binom{n-1}{i-1} \cdot f(i) \cdot g(n-i)\)
03 帶編號偶度點圖
構造方法是,對於前 \(n−1\) 個點任意連邊,第 \(n\) 個點向所有奇度點連邊(可以證明奇度點一定有偶數個)。
所以答案為 \(2^{\tbinom{n-1}{2}}\)。
04 帶編號偶度連通圖
與帶編號無向連通圖的計算方法類似。
令 \(g(n)\) 表示 \(n\) 個點的偶度點圖個數(由上可知 \(2^{\tbinom{n-1}{2}}\)),\(f(n)\) 表示 \(n\) 個點的偶度點連通圖個數。
枚舉偶度點圖中 \(1\) 號點所在連通塊的大小。
\(\displaystyle g(n)=\sum\limits_{i=1}^n \binom{n-1}{i-1} \cdot f(i) \cdot g(n-i)\)
\(\displaystyle f(n)=g(n)-\sum\limits_{i=1}^{n-1} \binom{n-1}{i-1} \cdot f(i) \cdot g(n-i)\)
九、無根樹 / Prufer 序列
01 無根樹轉 Prufer 序列
每次選擇一個編號最小的葉結點(度數為 \(1\))並刪掉它,然后在序列中記錄下它連接到的那個結點。重復 \(n-2\) 次后就只剩下兩個結點,算法結束。
-
Prufer 序列的長度為 \(n-2\)
-
每個點在 Prufer 序列中出現的次數為其度數 \(-1\)
02 Prufer 序列轉無根樹
每次選擇一個度數為 \(1\) 的最小的編號節點,與當前枚舉到的 Prufer 序列的點之間連上一條邊,然后同時將兩個點的度數 \(-1\)。最后在剩下的兩個度為 \(1\) 的點間連一條邊。
注意到,點數為 \(n\) 的無根樹與長度為 \(n−2\) 的 Prufer 序列一一對應。
03 完全圖的生成樹數量
注意到實際上要求的就是長度為 \(n-2\),值域為 \(1\sim n\) 的 Prufer 序列的數量,於是方案數即為 \(n^{n-2}\)。
04 給定 n 個節點的度數,求無根樹的個數
已知每個節點的度數,也就是說每個元素在 Prufer 序列中出現的次數是確定的(即 \(i\) 在數列中恰好出現 \(d_i-1\) 次),於是問題轉化成為求可重集的排列個數。答案即為:
\(\displaystyle\frac{(n-2)!}{\prod\limits_{i=1}^n (d_i-1)!}\)
05 計算完全二分圖 K(n,m) 的生成樹個數
注意到生成樹的 Prufer 序列中一定有 \(n-1\) 個左側的點,\(m-1\) 個右側的點,於是答案即為 \(n^{m-1} \cdot m^{n-1}\)。