概念
二項式反演為一種反演形式,常用於通過 “指定某若干個” 求 “恰好若干個” 的問題。
注意:二項式反演雖然形式上和多步容斥極為相似,但它們並不等價,只是習慣上都稱之為多步容斥。
引入
既然形式和多步容斥相似,我們就從多步容斥講起。
我們都知道:\(|A\cup B|=|A|+|B|-|A\cap B|\) ,這其實就是容斥原理。
它的一般形式為:
證明:
設某一元素被 \(m\) 個集合所包含,則其對左側的貢獻為 \(1\) ;
對右側的貢獻為 \(\sum\limits_{i=1}^m(-1)^{i-1}{m\choose i}=-\sum\limits_{i=1}^m(-1)^i{m\choose i}=1-\sum\limits_{i=0}^m(-1)^i{m\choose i}=1-(1-1)^m=1\) 。
故左側等於右側 ,證畢。
形式
形式零
沿用剛剛多步容斥的公式,記 \(A_i^c\) 表示 \(A_i\) 的補集,則將一般形式變形,可以得到:
同時,由於補集的補集就是原集,因此又有:
考慮一種特殊情況:多個集合的交集大小只和集合的數目有關。
記 \(f(n)\) 表示 \(n\) 個補集的交集大小,\(g(n)\) 表示 \(n\) 個原集的大小,則兩個公式分別可以寫成:
顯然這兩個公式是等價關系,更是相互推導的關系,於是我們得到了二項式反演的形式零:
形式一
公式如下:
證明一
在形式零中,令 \(h(n)=(-1)^ng(n)\) ,則形式零就變為了:
整理后就是形式一。
證明二
將右側代入左側,則:
考慮調換兩個求和符號的順序,即先枚舉 \(i\) ,再枚舉 \(j\) ,則又有:
考慮 \({n\choose i}{i\choose j}\) 的組合意義:從 \(n\) 個中選 \(i\) 個,再從 \(i\) 個中選 \(j\) 個。不妨反過來想,先從 \(n\) 個中選 \(j\) 個,再從剩下的 \(n-j\) 個中選出 \(i-j\) 個,即 \({n\choose j}{n-j\choose i-j}\) 。
於是可以得到:
當 \(n-j\neq 0\) 時,顯然 \((1-1)^{n-j}=0\) ;
當 \(n-j=0\) 時,出現 \(0^0\) 不能直接計算,需要使用組合形式求解,此時 \({n-j\choose t}(-1)^{t}=1\) 。
故 \(\sum\limits_{t=0}^{n-j}{n-j\choose t}(-1)^t=[j=n]\) ,於是:
左右恆等,證畢。
注:由於證明二並未用到 \(i\) 從 \(0\) 開始這一性質,因此更通用的公式為:
形式二
這個形式和形式一類似,是最常用的公式。公式如下:
證明
將右側代入左側,則:
左右恆等,證畢。
組合意義
記 \(f(n)\) 表示 “欽定選 \(n\) 個”,\(g(n)\) 表示 “恰好選 \(n\) 個”,則對於任意的 \(i\ge n\) ,\(g(i)\) 在 \(f(n)\) 中被計算了 \(i\choose n\) 次,故 \(f(n)=\sum\limits_{i=n}^m{i\choose n}g(i)\) ,其中 \(m\) 是數目上界。
注意:在定義中,\(f(n)\) 表示先欽定 \(n\) 個,再統計欽定情況如此的方案數,其中會包含重復的方案,因為一個方案可以有多種欽定情況。具體地,對於恰好選擇 \(i\) 個,欽定情況數位 \(i\choose n\) ,故 g(i) 在 \(f(i)\) 中被計算了 \(i\choose n\) 次。切勿將 \(f(n)\) 來理解為普通的后綴和。
例題
[bzoj2839]集合計數
題目大意
一個有 \(n\) 個元素的集合有 \(2^n\) 個不同子集(包含空集),現在要在這 \(2^n\) 個集合中取出至少一個集合,使得它們的交集的元素個數為 \(k\) ,求取法的方案數模 \(10^9+7\) 。
\(1\le n\le 10^6\) ,\(0\le k\le n\) 。
題解
對於稍有組合數學基礎的人,通過直覺很容易列出式子 \({n\choose i}(2^{2^{n-i}}-1)\) 。即欽定 \(i\) 個交集元素,則包含這 \(i\) 個的集合有 \(2^{n-i}\) 個;每個集合可選可不選,但不能都不選,由此可得此方案數。
接下來考慮上式與所求的關系:設 \(f(i)\) 表示欽定交集元素為某 \(i\) 個的方案數, \(g(i)\) 表示交集元素恰好為 \(i\) 個的方案數,則 \({n\choose k}(2^{2^{n-k}-1})=f(k)=\sum\limits_{i=k}^n{n\choose i}g(i)\) 。
通過二項式反演求出 \(g(k)=\sum\limits_{i=k}^n(-1)^{i-k}{i\choose k}f(i)=\sum\limits_{i=k}^n(-1)^{i-k}{i\choose k}{n\choose i}(2^{2^{n-i}}-1)\) 。
使用一些預處理手段,時間復雜度 \(O(n)\) 。
代碼
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll mod = 1000000007;
ll fac[1000010] , ine[1000010] , fi[1000010] , pow[1000010];
ll choose(ll n , ll m)
{
return fac[n] * fi[m] % mod * fi[n - m] % mod;
}
int main()
{
int n , k , i;
ll ans = 0;
scanf("%d%d" , &n , &k);
fac[0] = fac[1] = ine[1] = fi[0] = fi[1] = 1;
for(i = 2 ; i <= n ; i ++ )
{
fac[i] = fac[i - 1] * i % mod;
ine[i] = (mod - mod / i) * ine[mod % i] % mod;
fi[i] = fi[i - 1] * ine[i] % mod;
}
pow[0] = 2;
for(i = 1 ; i <= n ; i ++ ) pow[i] = pow[i - 1] * pow[i - 1] % mod;
for(i = k ; i <= n ; i ++ )
{
if((i & 1) == (k & 1)) ans = (ans + choose(n , i) * choose(i , k) % mod * (pow[n - i] - 1 + mod)) % mod;
else ans = (ans - choose(n , i) * choose(i , k) % mod * (pow[n - i] - 1 + mod) % mod + mod) % mod;
}
printf("%lld\n" , (ans + mod) % mod);
return 0;
}
[bzoj3622]已經沒有什么好害怕的了
題目大意
給出兩個長度均為 \(n\) 的序列 \(A\) 和 \(B\) ,保證這 \(2n\) 個數互不相同。現要將 \(A\) 序列中的數與 \(B\) 序列中的數兩兩配對,求 “ \(A>B\) 的對數比 \(A<B\) 的對數恰好多 \(k\) ” 的配對方案數模 \(10^9+9\) 。
題解
顯然當 \(n-k\) 為奇數時必然無解;當 \(n-k\) 為偶數時,\(A>B\) 的對數恰好為 \(\frac{n+k}2\) ,記 \(m=\frac{n+k}2\) 。
由於 “恰好” 這一限制不容易處理,考慮將其轉化為 “欽定” 限制,進而通過二項式反演來處理。
先將 \(A\) 和 \(B\) 從小到大排序,設 \(dp(i,j)\) 表示考慮了 \(A\) 的前 \(i\) 個數,欽定了 \(j\) 對 \(A>B\) 的方案數。
討論 \(A_i\) 的配對情況:若不配對,則方案數為 \(dp(i-1,j)\) ;若配對,記 \(B\) 中比 \(A_i\) 小的數的個數為 \(cnt(i)\) ,則方案數為 \(dp(i-1,j-1)\times (cnt(i)-(j-1))\) 。
故 \(dp(i,j)=dp(i-1,j)+dp(i-1,j-1)\times(cnt(i)-(j-1))\) 。
設 \(f(i)\) 表示欽定 \(i\) 對 \(A>B\) 的方案數,\(g(i)\) 表示恰好 \(i\) 對 \(A>B\) 的方案數,則 \((n-m)!\times dp(n,m)=f(m)=\sum\limits_{i=m}^n{i\choose m}g(i)\) 。
故 \(g(m)=\sum\limits_{i=m}^n(-1)^{i-m}{i\choose m}f(i)=\sum\limits_{i=m}^n(-1)^{i-m}{i\choose m}(n-i)!\times dp(n,i)\) 。
時間復雜度 \(O(n^2)\) 。
代碼
#include <cstdio>
#include <algorithm>
#define N 2010
#define mod 1000000009
using namespace std;
typedef long long ll;
int a[N] , b[N];
ll c[N][N] , fac[N] , f[N][N];
int main()
{
int n , k , i , j , p = 0;
ll ans = 0;
scanf("%d%d" , &n , &k);
if((n ^ k) & 1)
{
puts("0");
return 0;
}
k = (n + k) >> 1;
for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &a[i]);
for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &b[i]);
sort(a + 1 , a + n + 1) , sort(b + 1 , b + n + 1);
for(i = 0 ; i <= n ; i ++ ) f[i][0] = 1;
for(i = 1 ; i <= n ; i ++ )
{
while(p < n && b[p + 1] < a[i]) p ++ ;
for(j = 1 ; j <= n ; j ++ )
f[i][j] = (f[i - 1][j] + f[i - 1][j - 1] * (p - j + 1)) % mod;
}
c[0][0] = fac[0] = 1;
for(i = 1 ; i <= n ; i ++ )
{
c[i][0] = 1 , fac[i] = fac[i - 1] * i % mod;;
for(j = 1 ; j <= i ; j ++ )
c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;
}
for(i = k ; i <= n ; i ++ )
{
if((i ^ k) & 1) ans = (ans - c[i][k] * f[n][i] % mod * fac[n - i] % mod + mod) % mod;
else ans = (ans + c[i][k] * f[n][i] % mod * fac[n - i]) % mod;
}
printf("%lld\n" , ans);
return 0;
}
[bzoj4710]分特產
題目大意
有 \(n\) 個人和 \(m\) 種物品,第 \(i\) 種物品有 \(a_i\) 個,同種物品之間沒有區別。現在要將這些物品分給這些人,使得每個人至少分到一個物品,求方案數模 \(10^9+7\) 。
題解
對於多種物品的情況,“每個人至少分到一個物品” 是一個非常棘手的條件,考慮將其轉化為 “恰好 \(0\) 個人沒有分到物品” ,並用二項式反演來解決。
設 \(f(i)\) 表示欽定 \(i\) 個人沒有分到物品的方案數,\(g(i)\) 表示恰好 \(i\) 個人沒有分到物品的方案數,則在 \(f(t)\) 中,對於第 \(i\) 種物品,分配時相當於 \(a_i\) 個物品分給 \(n-t\) 個人,方案數為 \({n-t+a_i-1\choose a_i-1}\) , 於是 \({n\choose t}\prod\limits_{i=1}^m{n-t+a_i-1\choose a_i-1}=f(t)=\sum\limits_{i=t}^n{i\choose t}g(i)\) 。
故 \(g(t)=\sum\limits_{i=t}^n(-1)^{i-t}{i\choose t}f(i)=\sum\limits_{i=t}^n(-1)^{i-t}{i\choose t}{n\choose i}\prod\limits_{j=1}^m{n-i+a_j-1\choose a_j-1}\) 。
最終的答案 \(g(0)=\sum\limits_{i=0}^n(-1)^i{n\choose i}\prod\limits_{j=1}^m{n-i+a_j-1\choose a_j-1}\) 。
時間復雜度 \(O(n^2+nm)\) 。
代碼
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 2010
using namespace std;
typedef long long ll;
const ll mod = 1000000007;
ll c[N][N];
int w[N];
int main()
{
int n , m , i , j;
ll ans = 0 , tmp;
for(i = 0 ; i <= 2000 ; i ++ )
{
c[i][0] = 1;
for(j = 1 ; j <= i ; j ++ )
c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % mod;
}
scanf("%d%d" , &n , &m);
for(i = 1 ; i <= m ; i ++ ) scanf("%d" , &w[i]);
for(i = 0 ; i < n ; i ++ )
{
tmp = c[n][i];
for(j = 1 ; j <= m ; j ++ )
tmp = tmp * c[w[j] + n - i - 1][w[j]] % mod;
if(i & 1) ans = (ans - tmp + mod) % mod;
else ans = (ans + tmp) % mod;
}
printf("%lld\n" , ans);
return 0;
}