其他分享
首页 > 其他分享> > 二项式反演

二项式反演

作者:互联网

基本式子

\[f_n=\sum_{i=0}^n (-1)^iC_n^i g_i\Longleftrightarrow g_n=\sum_{i=0}^n (-1)^i C_n^i f_i \]

但平时做题一般都是用的另一种形态:

\[f_n=\sum_{i=0}^n C_n^i g_i \Longleftrightarrow g_n=\sum_{i=0}^n (-1)^{n-i} C_n^i f_i \]

还有一种:

\[f_n=\sum_{i=k}^n C_i^k g_i \Longleftrightarrow g_k=\sum_{i=k}^k (-1)^{i-k} C_i^k f_i \]

其中第一种是恰好和至多间的转换,第二种是恰好和至少之间的转换

证明的话可以看 这篇博客,讲的非常详细

例题

[HDU1465]不容易系列之一

Description

求所有 \(a_i \not= i\) 的长度为 \(n\) 的排列个数。

Sol

设 \(f_i\) 为错排数恰好为 \(i\) 的排列个数,那么我们的答案就是 \(f_n\),但 \(f\) 并不好求,我们考虑用 \(g_i\) 表示错排数至多为 \(i\) 的方案数,那么我们有:

\[g_n=\sum_{i=0}^n C_n^i f_i \]

很显然,我们固定 \(i\) 个位置为错排,有 \(C_n^i\) 中选法,每种选法的方案数为 \(f_i\)。

那么容易得知 \(g_n=n!\),那么我们就可以设 \(g_i=i!\),那么二项式反演一下,即可得:

\[f_i=\sum_{i=0}^n (-1)^{n-i}C_n^i i! \]

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int f[25], fac[25], C[25][25];
signed main() {
    fac[0] = 1;
    for(int i = 1; i <= 20; i++)  fac[i] = fac[i - 1] * i;
    C[1][1] = C[1][0] = 1;
    for(int i = 2; i <= 20; i++) {
        C[i][0] = 1;
        for(int j = 1; j <= i; j++)  C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
    }
    for(int i = 1; i <= 20; i++) {
        for(int j = 0; j <= i; j++)
            if((i - j) % 2 == 0)  f[i] += C[i][j] * fac[j];
            else  f[i] -= C[i][j] * fac[j];
    }
    int n;
    while(cin >> n)  cout << f[n] << endl;
    return 0;
}

[bzoj3622]已经没有什么好害怕的了

Description

给定 \(2\) 个长为 \(n\) 的数组 \(a,b\),将 \(a,b\) 中数两两配对,求 \(a_i>b_i\) 的组数比 \(a_i <b_i\) 的组数恰好多 \(k\) 的方案数。

\(1\le n\le 2000\)

Sol

首先将恰好转化为至少,设 \(f_j\) 为 \(a_i >b_i\) 的组数恰好 \(j\)的方案数,\(g_j\) 表示至少为 \(j\) 的方案数。

\[g_k=\sum_{i=k}^n C_i^k f_i \Longleftrightarrow f_k=\sum_{i=k}^n (-1)^{i-k} C_i^k g_i \]

对于这个 \(C_i^k\) 的意义,我们可以理解为:我们选择某 \(i\) 个位置时,它可以由 \(C_i^k\) 个只选 \(k\) 个数的方案往后选而得出,故此处的“至少”是有重复的。

那么我们考虑求 \(g_i\),我们将 \(a,b\) 排序,设 \(g_{i,j}\) 表示前 \(i\) 个数恰好有 \(j\) 个 \(a_k>b_k\),记 \(cnt_i\) 为前 \(i\) 个数中最大的 \(a>b\) 的个数,那么我们有方程式:

\[g_{i,j}=g_{i-1,j}+g_{i-1,j-1}\times (cnt_i-j+1) \]

注意此时的 \(g\) 的 \(n-i\) 个组合可以任意分配,所以 \(g_{n,i}\) 还需乘上 \((n-i)!\),得到二项式反演式子中的 \(g\),代表至少 \(i\) 个(可重)的方案数。

求出 \(g\) 后二项式反演即可。

Code

#include<bits/stdc++.h>
#define int long long
#define Mod 1000000009
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int n, k, fac[2505], C[2505][2505], a[2505], b[2505], cnt[2505], f[2505][2505];
signed main() {
    n = Read(), k = Read();
    if((n + k) % 2 != 0) {
        cout << 0;
        return 0;
    }
    k = (n + k) / 2;
    for(int i = 1; i <= n; i++)  a[i] = Read();
    for(int i = 1; i <= n; i++)  b[i] = Read();
    sort(a + 1, a + n + 1); sort(b + 1, b + n + 1);
    int nw = 0;
    for(int i = 1; i <= n; i++) {
        while(nw + 1 <= n && a[i] > b[nw + 1])  ++nw;
        cnt[i] = nw;
    }
    for(int i = 0; i <= n; i++)  f[i][0] = 1;
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= cnt[i]; j++) {
            if(j)  f[i][j] += f[i - 1][j - 1] * (cnt[i] - j + 1) % Mod;
            f[i][j] += f[i - 1][j]; f[i][j] %= Mod;
        }
    }
    fac[0] = 1;
    for(int i = 1; i <= 2500; i++)  fac[i] = fac[i - 1] * i % Mod;
    C[0][0] = 1;
    for(int i = 1; i <= 2500; i++) {
        C[i][0] = 1;
        for(int j = 1; j <= i; j++)  C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % Mod;
    }
    for(int i = 0; i <= n; i++)  (f[n][i] *= fac[n - i]) %= Mod;
    int ans = 0;
    for(int i = k; i <= n; i++) {
        if((i - k) & 1)  ans -= C[i][k] * f[n][i] % Mod;
        else  ans += C[i][k] * f[n][i] % Mod;
        ans = (ans % Mod + Mod) % Mod;
    }
    cout << ans << endl;
    return 0;
}

标签:ch,Longleftrightarrow,int,sum,个数,反演,二项式,getchar
来源: https://www.cnblogs.com/verjun/p/14168429.html