二項式反演及其應用

概念

二項式反演爲一種反演形式,常用於通過 “指定某若干個” 求 “恰好若干個” 的問題。

注意:二項式反演雖然形式上和多步容斥極爲相似,但它們並不等價,只是習慣上都稱之爲多步容斥。

引入

既然形式和多步容斥相似,我們就從多步容斥講起。

我們都知道:$|A\cup B|=|A|+|B|-|A\cap B|$ ,這其實就是容斥原理。

它的一般形式爲:

$$|A_1\cup A_2\cup...\cup A_n|=\sum\limits_{1\le i\le n}|A_i|-\sum\limits_{1\le i<j\le n}|A_i\cap A_j|+...+(-1)^{n-1}\times |A_1\cap A_2\cap ...\cap A_n|$$

證明:

設某一元素被 $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$ 的補集,則將一般形式變形,可以得到:
$$
|A_1^c\cap A_2^c\cap ...\cap A_n^c|=|S|-\sum\limits_{1\le i\le n}|A_i|+\sum\limits_{1\le i<j\le n}|A_i\cap A_j|-...+(-1)^n\times |A_1\cap A_2\cap ...\cap A_n|
$$
同時,由於補集的補集就是原集,因此又有:
$$
|A_1\cap A_2\cap ...\cap A_n|=|S|-\sum\limits_{1\le i\le n}|A_i^c|+\sum\limits_{1\le i<j\le n}|A_i^c\cap A_j^c|-...+(-1)^n\times |A_1^c\cap A_2^c\cap ...\cap A_n^c|
$$
考慮一種特殊情況:多個集合的交集大小隻和集合的數目有關。

記 $f(n)$ 表示 $n$ 個補集的交集大小,$g(n)$ 表示 $n$ 個原集的大小,則兩個公式分別可以寫成:
$$
f(n)=\sum\limits_{i=0}^n(-1)^i{n\choose i}g(i)\
g(n)=\sum\limits_{i=0}^n(-1)^i{n\choose i}f(i)
$$
顯然這兩個公式是等價關係,更是相互推導的關係,於是我們得到了二項式反演的形式零:
$$
f(n)=\sum\limits_{i=0}^n(-1)^i{n\choose i}g(i)\Leftrightarrow g(n)=\sum\limits_{i=0}^n(-1)^i{n\choose i}f(i)
$$

形式一

公式如下:
$$
f(n)=\sum\limits_{i=0}^n{n\choose i}g(i)\Leftrightarrow g(n)=\sum\limits_{i=0}^n(-1)^{n-i}{n\choose i}f(i)
$$

證明一

在形式零中,令 $h(n)=(-1)^ng(n)$ ,則形式零就變爲了:
$$
f(n)=\sum\limits_{i=0}^n{n\choose i}h(i)\Leftrightarrow \frac{h(n)}{(-1)^n}=\sum\limits_{i=0}^n(-1)^i{n\choose i}f(i)
$$
整理後就是形式一。

證明二

將右側代入左側,則:

$$
\begin{split}
f(n)&=\sum\limits_{i=0}^n{n\choose i}\sum\limits_{j=0}^i(-1)^{i-j}{i\choose j}f(j)\&=\sum\limits_{i=0}^n\sum\limits_{j=0}^i(-1)^{i-j}{n\choose i}{i\choose j}f(j)
\end{split}
$$

考慮調換兩個求和符號的順序,即先枚舉 $i$ ,再枚舉 $j$ ,則又有:
$$
f(n)=\sum\limits_{j=0}^nf(j)\sum\limits_{i=j}^n(-1)^{i-j}{n\choose i}{i\choose 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}$ 。

於是可以得到:
$$
\begin{split}
f(n)&=\sum\limits_{j=0}^n{n\choose j}f(j)\sum\limits_{i=j}^n{n-j\choose i-j}(-1)^{i-j}\
&=\sum\limits_{j=0}^n{n\choose j}f(j)\sum\limits_{t=0}^{n-j}{n-j\choose t}(-1)^t1^{n-j-t}\
&=\sum\limits_{j=0}^n{n\choose j}f(j)(1-1)^{n-j}
\end{split}
$$
當 $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]$ ,於是:
$$
f(n)={n\choose n}f(n)=f(n)
$$
左右恆等,證畢。

注:由於證明二並未用到 $i$ 從 $0$ 開始這一性質,因此更通用的公式爲:
$$
f(n)=\sum\limits_{i=m}^n{n\choose i}g(i)\Leftrightarrow g(n)=\sum\limits_{i=m}^n(-1)^{n-i}{n\choose i}f(i)
$$

形式二

這個形式和形式一類似,是最常用的公式。公式如下:
$$
f(n)=\sum\limits_{i=n}^m{i\choose n}g(i)\Leftrightarrow g(n)=\sum\limits_{i=n}^m(-1)^{i-n}{i\choose n}f(i)
$$

證明

將右側代入左側,則:
$$
\begin{split}
f(n)&=\sum\limits_{i=n}^m{i\choose n}\sum\limits_{j=i}^m(-1)^{j-i}{j\choose i}f(j)\
&=\sum\limits_{i=n}^m\sum\limits_{j=i}^m(-1)^{j-i}{i\choose n}{j\choose i}f(j)\
&=\sum\limits_{j=n}^mf(j)\sum\limits_{i=n}^j(-1)^{j-i}{i\choose n}{j\choose i}\
&=\sum\limits_{j=n}^m{j\choose n}f(j)\sum\limits_{i=n}^j{j-n\choose j-i}(-1)^{j-i}\
&=\sum\limits_{j=n}^m{j\choose n}f(j)\sum\limits_{t=0}^{j-n}{j-n\choose t}(-1)^{t}1^{j-n-t}\
&=\sum\limits_{j=n}^m{j\choose n}f(j)(1-1)^{j-n}\
&=\sum\limits_{j=n}^m{j\choose n}f(j)[j=n]\
&={n\choose n}f(n)\
&=f(n)
\end{split}
$$

左右恆等,證畢。

組合意義

記 $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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章