二项式反演学习笔记

· · 算法·理论

二项式反演学习笔记

简述二项式反演

对于两个函数 f(x),g(x),有:

f(x)=\sum_{i=x}^{n} \binom{i}{x} g(i) \Leftrightarrow g(x)=\sum_{i=x}^{n} (-1)^{i-x} \binom{i}{x} f(i)

证明如下(左推右)。

先给出一个引理:

\binom{i}{x} \binom{j}{i}=\binom{j}{x} \binom{j-x}{i-x}

证明见提示:(可以自己先试着推一推)

:::info[证明] 考虑左右两边的组合意义,左边代表先从 j 个里选出 i 个,再从 i 个里选出 x 个,也就是将 x 个选了两次,i-x 个选了一次。

右面是先决定哪 x 个选了两次,再决定剩下选了一次的是哪 i-x 个,因此左右两边相等 。

我们有:

\sum_{i=x}^{n} (-1)^{i-x} \binom{i}{x} \sum_{j=i}^{n} \binom{j}{i} g(j)\\ =\sum_{i=x}^{n} (-1)^{i-x} \sum_{j=i}^{n} \binom{i}{x} \binom{j}{i} g(j) \\ =\sum_{i=x}^{n} (-1)^{i-x} \sum_{j=i}^{n} \binom{j-x}{i-x} \binom{j}{x} g(j) \\ =\sum_{j=x}^{n} \binom{j}{x} \sum_{i=x}^{j} (-1)^{i-x} \binom{j-x}{i-x} g(j) \\ =\sum_{j=x}^{n} \binom{j}{x} g(j) \sum_{i=x}^{j} (-1)^{i-x} \binom{j-x}{i-x} \\ =\sum_{j=x}^{n} \binom{j}{x} g(j) [j=x] \\ =g(j)

故原命题得证,Q.E.D!

相似的,我们也有:

f(x)=\sum_{i=1}^{x} \binom{x}{i} g(i) \Leftrightarrow g(x)=\sum_{i=1}^{x} (-1)^{x-i} \binom{x}{i} f(i)

推广到二维,我们有:

f(x,y)=\sum_{i=1}^x \sum_{j=1}^y \binom{x}{i} \binom{y}{j} g(i,j) \\ \Leftrightarrow g(x,y)=\sum_{i=1}^x \sum_{j=1}^y (-1)^{x+y-i-j} \binom{x}{i} \binom{y}{j} f(i,j)

证明考虑令函数 h(x,y)=\sum_{j=1}^y \binom{y}{j} g(i,j) 然后对 ghhf 做两次二项式反演。

应用场景

当我们要求的问题具有限制:某种操作或选择恰好做 x 次的方案数时,如果直接做很难做,可以转化为要求该操作至多(少)做了 i 次,然后二项式反演即可,具体见例题。

例题

T1 [JSOI2015] 染色问题

给一个 n \times m 的矩形染色,共有 c 种颜色,每个格子可染可不染,要求每行每列至少有一个格子被染色,每种颜色至少使用一次。

直接做显然是不好做的,令 f(i,j,k) 表示恰好染了 i 行,j 列,使用了 k 种颜色,考虑对他进行二项式反演。

g(i,j,k) 表示恰好染了 i 行,j 列,使用了至多 k 种颜色,显然的,我们有:

g(i,j,k)=\sum_{x=1}^k \binom{k}{i} f(i,j,x)

f(n,m,c)=\sum_{x=1}^c (-1)^{c-x} \binom{c}{x} g(n,m,x)

此时依然不好做,考虑继续反演,记 h(i,j,k) 表示至多染了 i 行,j 列,使用了至多 k 种颜色的方案数,我们有。

h(x,y,k)= \sum_{i=1}^x \sum_{j=1}^y \binom{x}{i} \binom{y}{j} g(i,j,k)

故:

g(x,y,k)= \sum_{i=1}^x \sum_{j=1}^y (-1)^{x+y-i-j} \binom{x}{i} \binom{y}{j} h(i,j,k)

显然的,我们有:

h(i,j,k)=(k+1)^{ij}

此时其实可以直接做了,\mathcal O(nmc) 的复杂度完全可以接受,但还能继续优化。

g(x,y,k)= \sum_{i=1}^x \sum_{j=1}^y (-1)^{x+y-i-j} \binom{x}{i} \binom{y}{j} h(i,j,k) \\ = \sum_{i=1}^x (-1)^{x-i} \binom{x}{i} \sum_{j=1}^y (-1)^{y-j} \binom{y}{j} (k+1)^{ij}\\ =\sum_{i=1}^x (-1)^{x-i} \binom{x}{i} ((k+1)^{i}-1)^y

故:

f(n,m,c)=\sum_{x=1}^c (-1)^{c-x} \binom{c}{x} \sum_{i=1}^n (-1)^{n-i} \binom{n}{i} ((k+1)^{i}-1)^y \\ =\sum_{x=1}^c \sum_{i=1}^n (-1)^{n+c-x-i} \binom{n}{i} \binom{c}{x} ((k+1)^{i}-1)^y

复杂度(默认 n,m,c 同阶) \mathcal O(n^2\log n)

:::success[code]

#include<bits/stdc++.h>
using namespace std;

#define int long long
#define PII pair<int,int>
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define ivoid inline void
ivoid wsp(int x){
    cout<<x<<" ";
}
ivoid wln(int x){
    cout<<x<<"\n";
}
ivoid wstr(string s){
    cout<<s<<"\n";
}
#define cout cerr

const int maxn=410;
const int mod=1e9+7;
int n,m,c,f[maxn],g[maxn],inv[maxn],fac[maxn],po[2]={1,-1};

void init(){
    inv[0]=inv[1]=fac[0]=fac[1]=1;
    for(int i=2;i<=400;i++){
        inv[i]=inv[mod%i]*(mod-(mod/i))%mod;
        fac[i]=fac[i-1]*i%mod;
    }
    for(int i=2;i<=400;i++) inv[i]=inv[i]*inv[i-1]%mod;
}

int C(int a,int b){
    if(a>b||a<0) return 0;
    else if(a==0||a==b) return 1;
    else return fac[b]*inv[a]%mod*inv[b-a]%mod;
}

int qp(int a,int b){
    int ans=1;
    while(b){
        if(b&1) ans=(ans*a)%mod;
        a=a*a%mod,b>>=1;
    }
    return ans;
}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>c;
    init();
    int ans=0;
    for(int i=0;i<=c;i++){
        for(int j=0;j<=m;j++){
            ans=(ans+((C(i,c)*C(j,m)%mod*qp((qp(i+1,j)-1+mod)%mod,n))%mod*po[(c+m-i-j)&1])+mod)%mod;
        }
    }
    wln(ans);
    return 0;
}

:::

T2 Sky Full of Stars

我们发现至少一行和至少一列这个限制很不好做,考虑转化成非零行且零列然后容斥。

f_{i,j} 表示至少有 ij 列同色的情况数,显然我们有:

Ans=\sum_{i=0}^n \sum_{j=0}^n (-1)^{i+j} \binom{n}{i} \binom{n}{j} f_{i,j} \\ f_{i,j}= \begin{cases} \binom{n}{i} \binom{n}{j} 3^{(n-i)(n-j)+1}&i,j \ne 0 \\ 3^{i} \binom{n}{i} 3^{n^2-in} &i \ne 0 \wedge j=0 \\ 3^{j} \binom{n}{j} 3^{n^2-jn} &i = 0 \wedge j\ne 0 \\ 3^{n^2} &i=j=0 \end{cases}

二三四式直接暴力即可,无需优化,考虑一式如何优化,我们有:

sum_1=\sum_{i=1}^n \sum_{j=1}^n (-1)^{i+j} \binom{n}{i} \binom{n}{j} 3^{(n-i)(n-j)+1} \\ =3^{n^2+1} \sum_{i=1}^n \binom{n}{i} 3^{-in} (-1)^i \sum_{j=1}^n (-1)^j \binom{n}{j} 3^{j(i-n)} \\ =3^{n^2+1} \sum_{i=1}^n \binom{n}{i} 3^{-in} (-1)^i (\sum_{j=0}^n (-1)^j \binom{n}{j} 3^{j(i-n)}-1) \\ =3^{n^2+1} \sum_{i=1}^n \binom{n}{i} 3^{-in} (-1)^i ((1-3^{i-n})^{n}-1)

直接计算即可,复杂度 \mathcal O(n \log n)

:::success[code]

#include<bits/stdc++.h>
using namespace std;

#define int long long
#define PII pair<int,int>
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define ivoid inline void
ivoid wsp(int x){
    cout<<x<<" ";
}
ivoid wln(int x){
    cout<<x<<"\n";
}
ivoid wstr(string s){
    cout<<s<<"\n";
}
#define cout cerr

const int maxn=1e6+10;
const int mod=998244353;
int n,sum1=0,sum2=0,ans=0;
int inv[maxn],fac[maxn];

void init(){
    inv[0]=inv[1]=fac[0]=fac[1]=1;
    for(int i=2;i<=1e6;i++){
        inv[i]=inv[mod%i]*(mod-(mod/i))%mod;
        fac[i]=fac[i-1]*i%mod;
    }
    for(int i=2;i<=1e6;i++) inv[i]=inv[i]*inv[i-1]%mod;
}

int C(int a,int b){
    if(a>b||a<0) return 0;
    else if(a==0||a==b) return 1;
    else return fac[b]*inv[a]%mod*inv[b-a]%mod;
}

int qp(int a,int b){
    int ans=1;b%=(mod-1);
    while(b){
        if(b&1) ans=(ans*a)%mod;
        a=a*a%mod,b>>=1;
    }
    return ans%mod;
}

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    init();
    int c=-1;
    for(int i=1;i<=n;i++){
        sum1=(sum1+((c+mod)%mod*C(i,n)%mod*qp(qp(3,i*n),mod-2)%mod*(qp((1-qp(qp(3,n-i),mod-2)+mod)%mod,n)-1+mod)%mod)%mod)%mod;
        c=-c;
    }
    sum1=sum1*qp(3,n*n+1)%mod;
//  cout<<sum1<<"\n";
    c=-1;
    for(int i=1;i<=n;i++){
        sum2=(sum2+((c+mod)%mod*qp(3,i)%mod*C(i,n)%mod*qp(3,n*(n-i))%mod))%mod;
        c=-c;
    }
    sum2*=2;
    wln(mod-((sum1+sum2)%mod));
    return 0;
}

:::

T3 已经没有什么好害怕的了

k \leftarrow \frac{n+k}{2}

考虑如何钦定一些数使得他们被选择,很自然的想到 dp,首先,将所有数放到一个序列上从小到大排序,记 dp_{i,j} 表示前 i 个数钦定了 ja,指向左侧比它小的数,显然有转移:

dp_{i,j}=dp_{i-1,j}+(cnt-j+1)dp_{i-1,j-1} 记 $f_i=dp_{2n,i}(n-i)!$ 表示至少存在 $i$ 个 $a$ 使得它们比匹配的 $b$ 大的情况数,套路化的直接二项式反演即可得到答案: $$ Ans_k=\sum_{i=k}^n (-1)^{i-k}\binom{i}{k} f_i $$ :::success[code] ``` #include<bits/stdc++.h> using namespace std; #define int long long #define PII pair<int,int> #define fi first #define se second #define mk make_pair #define pb push_back #define ivoid inline void ivoid wsp(int x){ cout<<x<<" "; } ivoid wln(int x){ cout<<x<<"\n"; } ivoid wstr(string s){ cout<<s<<"\n"; } #define cout cerr const int maxn=4e3+10; const int mod=1e9+9; struct node{ int w,type; }a[maxn];int tp=0; bool cmp(node x,node y){return x.w<y.w;} int n,k,cnt=0; int dp[maxn][maxn],f[maxn],g[maxn],inv[maxn],fac[maxn]; void init(){ inv[0]=inv[1]=fac[0]=fac[1]=1; for(int i=2;i<=n;i++){ inv[i]=inv[mod%i]*(mod-(mod/i))%mod; fac[i]=fac[i-1]*i%mod; } for(int i=2;i<=n;i++) inv[i]=inv[i]*inv[i-1]%mod; } int C(int a,int b){ if(a>b||a<0) return 0; else if(a==0||a==b) return 1; else return fac[b]*inv[a]%mod*inv[b-a]%mod; } signed main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n>>k;init(); if((n+k)&1){ wln(0); return 0; } k=(n+k)/2; for(int i=1;i<=n;i++){ int x;cin>>x; a[++tp]=(node){x,1}; } for(int i=1;i<=n;i++){ int x;cin>>x; a[++tp]=(node){x,2}; } sort(a+1,a+tp+1,cmp); dp[0][0]=1; for(int i=1;i<=tp;i++){ cnt+=(a[i].type==1); for(int j=0;j<=i;j++){ if(a[i].type==1){ dp[i][j]=(i-cnt-(j-1))*dp[i-1][j-1]%mod; } dp[i][j]=(dp[i-1][j]+dp[i][j])%mod; } } for(int j=1;j<=n;j++) f[j]=dp[tp][j]*fac[n-j]%mod; // for(int j=1;j<=n;j++) cout<<f[j]<<"\n"; int ans=0,c=1; for(int i=k;i<=n;i++){ ans=(ans+(c*(C(k,i)*f[i]%mod)+mod))%mod; c=-c; } wln(ans); return 0; } ```