二项式反演
作者:互联网
基本式子
\[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