【容斥原理】
對於統計指定排列方案數的問題,一個方案是空間中的一個元素。
定義集合x是滿足排列中第x個數的限定條件的方案集合,設排列長度為S,則一共S個集合。
容斥原理的本質是考慮[集合交 或 集合交的補集]和[集合並 或 集合並的補集]之間相互轉化的問題。
定義目標函數為f(m),已知函數g(T)。(例如已知集合並,則T表示所有T個集合的集合並,通常g(T)=C(n,T)*T個集合的集合並)
當兩者都不是補集或兩者都是補集時,有f(S)=Σ(-1)|T|-1g(T),其中T為S的非空子集,即奇加偶減。
當兩者中有且僅有一者是補集時,有f(S)=Σ(-1)|T|g(T),其中T為S的子集,要把空集的補集(即全集)算入。
要特別注意補集的情況下,g(T)表示T的補集,記住無論如何原集從小到大。
例如已知集合並,求解集合交,容斥原理就是將所有集合組合方案的集合並乘以由集合個數決定的容斥系數,得到S個集合的集合交。
莫比烏斯函數μ相關的容斥:對於統計指定數字數量的問題,一個數字是空間中的一個元素,定義集合x(x是素數)是含有素因子x的數字集合。
那么枚舉所有數字就是枚舉集合交,μ就是(-1)^k(k是素因子個數)即容斥系數。
最后,當限制條件取反時,交集變成並集的補集,並集變成交集的補集。
★自帶容斥:可以省略系數的計算,對每個數計算【集合減去內部所有包含的交集】,然后求和即可。
具體見:【CodeForces】585 E. Present for Vitalik the Philatelist
【SRM20】數學場 T2
看到計數想容斥。
例題:
1.對一個n*m的棋盤染色,每格可以是黑色或白色,要求每行每列都有黑格。
定義一個集合為指定行列含有黑格,則題目要求集合交。
只有集合並的補集可以計算,即一些行一些列全是白格(是一些行一些列有黑格的補集),枚舉行列,則選出i行j列的方案數是C(n,i)C(m,j),這i行j列全是白格的方案數是2(n-i)(m-j)。
所以答案就是Σ(-1)i+jC(n,i)C(m,j)2(n-i)(m-j),0<=i<n,0<=j<m。
2.對一個n*m的棋盤染色,每格可以是黑色或白色,要求存在一行或一列全是黑格。
定義一個集合為指定行列含有白格,則題目要求集合交的補集,仍然用集合並的補集求解。
ans=Σ(-1)i+j-1C(n,i)C(m,j)2(n-i)(m-j)(狀態(0,1)和(1,0))。
3.對一個n*m的棋盤染色,每格可以是黑色或白色,要求存在一行和一列全是黑格。
定義一個集合為指定行列含白格,則題目要求兩個集合交的補集。
需要滿足的條件是:(A)存在一行都是黑格。(B)存在一列都是黑格。
先對A用容斥原理,答案為Σ(-1)i-1C(n,i)*(有i行都是黑格且滿足B的方案數)。(暫將B作為全局約束)
再對B用容斥原理,答案為Σ(-1)i-1C(n,i)*Σ(-1)j-1C(m,j)*(有i行j列都是黑格的方案數)。
化簡一下就是Σ(-1)i+jC(n,i)C(m,j)2(n-i)(m-j),其中i,j>=1。
4.倍數相關的容斥,莫比烏斯函數μ可以作為系數。
5.錯排問題:一種排列錯排為1,不錯排為0,求解集合交,已知集合並的補集,即ans=Σ(-1)^i*C(n,i)*(n-i)!。
【排列組合】
<加法原理>做一件事情有n個方法,第i個方法有pi種方案,則一共有p1+p2+...+pn種方案。
<乘法原理>做一件事件有n個步驟,第i個步驟有pi種方案,則一共有p1p2...pn種方案。
乘法原理是加法原理的特殊情況,加法原理的關鍵是不重不漏地分類,若有重復可以考慮容斥原理。
<排列>A(n,m)表示在n個數中選m個的排列數(n為下標,m為上標)
由乘法原理,每個步驟選擇一個數,則分別有n,n-1,...n-m+1種選擇,則可以簡單地表示為:
A(n,m)=n*(n-1)*...*(n-m+1)即
A(n,m)=n!/(n-m)!
特別地,n個數的全排列是A(n,n)=n!
<組合>C(n,m)表示在n個數中選m個的組合數(n為下標,m為上標)
首先選出m個數的組合,然后把這m個數進行全排列。由乘法原理知A(n,m)=C(n,m)*A(m,m),即C(n,m)=A(n,m)/A(m,m)=n!/((n-m)!m!)。
C(n,m)=n!/((n-m)!m!)
<組合數性質>
1.C(n,0)=C(n,n)=1
2.核心性質一:C(n,k)=C(n,n-k) 組合的對稱性!
3.核心性質二:C(n+1,k+1)=C(n,k)+C(n,k+1) 組合數的遞推公式(楊輝三角)。
證明:n+1個數里面選擇k+1個數有兩類方法,若選擇第一個數則轉化為C(n,k),否則轉化為C(n,k+1)。
4.C(n,k+1)=C(n,k)*(n-k)/(k+1) 組合數同下標遞推公式
直接利用公式變換:
C(n,k+1)=n(n-1)...(n-k+1)(n-k)/(k+1)!
C(n,k)=n(n-1)...(n-k+1)/k!
兩式相除得C(n,k+1)/C(n,k)=(n-k)/(k+1)
此性質可用於二項式定理(后面介紹)的預處理過程。
★性質:ΣC(n,0~n)=2^n。
5.重復元素問題:排列組合往往不是簡單的C和A公式一寫就可以解決的,因為有大量的重復元素,需要做大量去重工作。
(1)有重復元素的全排列。
有k個元素,其中第i個元素有ni個,求全排列個數。
令總數為n,設答案為x。對於答案中的每個排列,對相同元素標號后,相同元素內部可以再進行全排列,所以:
n1!n2!n3!...nk!x=n!得到x=n!/(n1!n2!n3!...nk!)
這里和組合數的推導一樣,從而得到結論:加上m個數的標號就是乘m個數的全排列,去除m個數的標號就是除以m個數的全排列。
(2)★可重復選擇的組合。
問題:n個數中取k個,每個數可以取多次,求組合。
試着反過來考慮,把k個1划分成n段的方案數。
問題轉化為求解x1+x2+...+xn=k的非負整數解個數,由於0很麻煩,所以令xi=xi+1,則轉化為:
求解x1+x2+...+xn=k+n的正整數解的個數。
想象有k+n個“1”排成一排,則問題等價於把這些“1”分成n個部分的方法數。
隔板法:重復問題中常轉化為x1+x2...+xn=X的形式,然后可以想象為在X-1個間隔中放置n-1個隔板。當隔出區間可能為0時,整體+1。
那么在k+n-1個間隔中選n-1個,即ans=C(k+n-1,n-1)=C(n+k-1,k)。
6.正難則反的思想(補集思想),在這類數學問題中尤其常用,而且往往十分隱蔽、不易發現。

#include<cstdio> #include<cstring> #include<cctype> #include<cmath> #include<algorithm> #define ll long long using namespace std; int read(){ char c;int s=0,t=1; while(!isdigit(c=getchar()))if(c=='-')t=-1; do{s=s*10+c-'0';}while(isdigit(c=getchar())); return s*t; } int min(int a,int b){return a<b?a:b;} int max(int a,int b){return a<b?b:a;} int abs(int x){return x>0?x:-x;} void mins(int &a,int b){if(a>b)a=b;} void maxs(int &a,int b){if(a<b)a=b;} //void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;} /*------------------------------------------------------------*/ const int inf=0x3f3f3f3f,MOD=1000000007; ll n; void gcd(ll a,ll b,ll& d,ll& x,ll& y){ if(!b){d=a;x=1;y=0;} else{gcd(b,a%b,d,y,x);y-=x*(a/b);} } ll inv(ll a,ll n){ ll d,x,y; gcd(a,n,d,x,y); return (x%n+n)%n; } ll fac[1010],fav[1010]; ll C(ll n,ll m){return fac[n]*fav[m]%MOD*fav[n-m]%MOD;} int main(){ fac[0]=1;fav[0]=1; for(int i=1;i<=1001;i++)fac[i]=fac[i-1]*i%MOD; for(int i=1;i<=1001;i++)fav[i]=inv(fac[i],MOD); return 0; }
例題:
1.n個數字的和不超過m的的方案數。
若是等於m,則求C(n+m-1,n-1)即(n+m-1,m)。
轉化為求ΣC(n+i-1,i),由組合數遞推公式C(n,m)=C(n-1,m-1)+C(n-1,m)得
C(n-1,0)+C(n,1)+C(n+1,2)+C(n+2,3)+...+C(n+m-1,m)
=C(n,0)+C(n,1)+C(n+1,2)+C(n+2,3)+...+C(n+m-1,m)
=C(n+m,m) 兩兩一步一步湊剩一個數字。
2.袋球模型
①n個不同的球和m個不同的袋子
每個球都有m中選擇,ans=m^n。
②n個相同的球和m個不同的袋子
視為將n+m個'1'分成m段,所以ans=C(n+m-1,m-1)。
③n個不同的球和m個相同的袋子——將1~n划分成m份的方案數
設f[i][j]表示將1~i划分成j份(每份不為0)的方案數,考慮數字 i 可以新開一個袋子,或放到之前的袋子。(強制不為0是因為從0變成1時不會重復)
所以f[i][j]=f[i-1][j-1]+f[i-1][j]*j。然后再統計Σf[n][i]。
④n個相同的球和m個相同的袋子——將n划分成m個非負整數的方案數
設f[i][j]表示將 i 划分成 j 個非負整數的方案數,如果方案有0則依賴於f[i][j-1],否則整體-1依賴於f[i-j][j]。
所以f[i][j] = f[i][j-1] + f[i-j][j] 。
<二項式定理>
觀察楊輝三角可以發現,它的第i行的數字是C(i-1,0),C(i-1,1)...C(i-1,i-1),即同一行同下標,上標從0開始遞增。
由楊輝三角也可以發現組合數的兩大核心性質(同行左右對稱,每個數等於上面和上面左邊兩數之和)。
另一方面,把(a+b)^n展開的多項式系數和楊輝三角一致,由此可得
★二項式定理:
其中組合數對應楊輝三角也對應系數,后邊對應項的內容。
也可以理解為n個括號,多少個選a,多少個選b出來的結果。
而求解系數可以使用組合數性質4。
【卡特蘭數】算法專題:卡特蘭數(計數數列)——onion_cyc
【集合划分數】【算法專題】集合划分數(斯特林數與貝爾數)
【Prufer序列】帶標號無根樹計數
參考:樹的計數 + prufer序列與Cayley公式 學習筆記
一、構造Prufer序列:對於一棵帶標號無根樹,定義度為1的節點為“葉子節點”,每次找到編號最小的葉子節點刪除,並將其鄰點加入Prufer序列,重復直至剩余兩個點為止(故Prufer序列的長度為n-2)。
構造過程用堆維護葉子節點即可,復雜度O(n log n)。
二、還原無根樹:對於點集{1,2,...,n},每次選擇最小的不在Prufer序列中的編號x,以及Prufer序列的第一個數字y(最早加入的),將x和y連邊,然后從點集種刪除x,從Prufer序列中刪除y。重復以上過程直至Prufer序為空,此時將點集中剩余的兩個編號連邊即可。
證明:還原的思路是通過Prufer序列依次找到原來的刪除點。最開始的時候,在Prufer序列中的點說明度不為1(鄰點刪除會將其加入Prufer序),所以不在Prufer序中的編號最小的節點就是最先刪除的“編號最小的葉子節點”,將這個點和Prufer序的第一個數連邊(其鄰點)連邊。然后從點集中刪除就是該點已經不能再刪,從Prufer序中刪除就是將度-1。
這也很容易看出,點x在Prufer序中的出現次數=點x的度-1。
還原過程先將不在Prufer序中的節點加入堆,過程中維護這個堆即可,復雜度O(n log n)。
三、推廣:
① Cayley公式:由於由數字1~n構成的長度為n-2的Prufer序列和n個點的無根樹一一對應,故n個點的無根樹數量為$n^{n-2}$(Prufer序的每一位可以是1~n),這也是n個點的完全圖的生成樹個數。
②指定點度的無根樹計數:如果要求點度序列是$D_1,D_2,...,D_n$(有解必須滿足和為2n-2和除非n=1否則度不為0),那相當於把n-2個數排列后進行消序,即$\frac{(n-2)!}{(D_1-1)!...(D_n-1)!}$。
例題:BZOJ 1211: [HNOI2004]樹的計數(這題要注意中間答案可能爆,要分解素因數上下減然后再乘起來)
例題:BZOJ 1005 明明的煩惱。(n個點指定度數,m個點未知。先將剩余度數視為同一個數,然后內部再任意分配m^left)