二项式反演学习笔记

Warriors_Cat

2020-12-01 16:05:25

Personal

~~诶不是我 NOIp 前为什么要开这个坑(~~ 对这个东西刚入门,有错轻喷。 sto 反演带师 [MCAdam](https://www.luogu.com.cn/user/81238) orz ## 0. 前置知识 ### 二项式定理: $$(a+b)^n=\sum_{i=0}^n\binom{n}{i}a^ib^{n-i}$$ 常见形式: $$(x+1)^n=\sum_{i=0}^n\binom{n}{i}x^i$$ $$(x-1)^n=\sum_{i=0}^n(-1)^i\binom{n}{i}x^i$$ $$0=(1-1)^n=\sum_{i=0}^n(-1)^i\binom{n}{i}$$ ### 组合恒等式 $$\binom{n}{i}\binom{i}{j}=\binom{n}{j}\binom{n - j}{i - j}$$ * 证明 $1:$ $$\begin{aligned}\mathrm{LHS}&=\dfrac{n!}{(n-i)!\cdot i!}\cdot\dfrac{i!}{(i-j)!\cdot j!}\\&=\dfrac{n!}{(n-j)!\cdot j!}\cdot\dfrac{(n-j)!}{[(n-j)-(i-j)]!\cdot(i-j)!}\\&=\binom{n}{j}\binom{n-j}{n-i}\end{aligned}$$ $$\mathcal{Q.E.D}$$ * 证明 $2:$ 考虑组合意义。 左式相当于从 $n$ 只猫中选 $i$ 只猫,再从这 $i$ 只猫中选 $j$ 只猫的方案数。 那么我们可以转化为从 $n$ 只猫中选 $j$ 只猫,再从剩下的 $n-j$ 只猫中选 $i-j$ 只猫来使得包含关系仍然成立。 于是就有原式。 $$\mathcal{Q.E.D}$$ 这两个式子**很重要!!很重要!!很重要!!** ## 1. 基本公式 二项式反演最核心的一条公式: $$f(n) = \sum_{i=0}^n(-1)^i\binom{n}{i}g(i) \iff g(n)=\sum_{i=0}^n(-1)^i\binom{n}{i}f(i)$$ ~~没错就是这么对称~~ * 证明: 考虑 $n$ 个集合 $A_1 - A_n$。 根据容斥原理可得 $$|A_1\cap A_2\cap...\cap A_n|=|S|+\sum_{i=1}^n(-1)^i \sum|\bar{A_{x_1}}\cap \bar{A_{x_2}}\cap...\cap \bar{A_{x_i}}|$$ $$|\bar{A_1}\cap \bar{A_2}\cap...\cap \bar{A_n}|=|S|+\sum_{i=1}^n(-1)^i \sum|A_{x_1}\cap A_{x_2}\cap...\cap A_{x_i}|$$ 如果有 $$f(i)=|A_{x_1}\cap A_{x_2}\cap...\cap A_{x_i}|$$ $$g(i)=|\bar{A_{x_1}}\cap \bar{A_{x_2}}\cap...\cap \bar{A_{x_i}}|$$ 那么就有 $$f(n)=\sum_{i=0}^n(-1)^i\binom{n}{i}g(i)$$ $$g(n)=\sum_{i=0}^n(-1)^i\binom{n}{i}f(i)$$ 显然这两个有等价和相互推导的关系,于是: $$f(n) = \sum_{i=0}^n(-1)^i\binom{n}{i}g(i) \iff g(n)=\sum_{i=0}^n(-1)^i\binom{n}{i}f(i)$$ $$\mathcal{Q.E.D}$$ 然而,尽管这个式子很对称,我们并不常用。 常用的是下面两个式子: $$1.f(n)=\sum_{i=0}^n\binom{n}{i}h(i) \iff h(n)=\sum_{i=0}^n(-1)^{n-i}\binom{n}{i}f(i)$$ * 证明: 令 $h(n) = (-1)^ng(n)$。 带回那个对称式: $$f(n)=\sum_{i=0}^n(-1)^i\binom{n}{i}g(i)=\sum_{i=0}^n\binom{n}{i}h(i)$$ $$\dfrac{h(n)}{(-1)^n}=\sum_{i=0}^n(-1)^i\binom{n}{i}f(i) \iff h(n)=\sum_{i=0}^n(-1)^{i+n}\binom{n}{i}f(i)$$ 由于 $n+i$ 与 $n-i$ 奇偶性相同,于是 $$f(n)=\sum_{i=0}^n\binom{n}{i}h(i) \iff h(n)=\sum_{i=0}^n(-1)^{n-i}\binom{n}{i}f(i)$$ $$\mathcal{Q.E.D}$$ 另外,这个式子还可以改写成: $$f(n)=\sum_{i=m}^n\binom{n}{i}h(i) \iff h(n)=\sum_{i=m}^n(-1)^{n-i}\binom{n}{i}f(i)$$ 证明的话……用用前缀和思想就行了? $$2.f(n)=\sum_{i=n}^m\binom{i}{n}h(i) \iff h(n)=\sum_{i=n}^m(-1)^{i-n}\binom{i}{n}f(i)$$ 证明同理,这里就不赘述了。 ## 2.常见用法 那,这个鬼东西到底有什么用呢? 比如说,我们观察这个式子: $$f(n)=\sum_{i=n}^m\binom{i}{n}g(i)$$ 如果我们定义 $f(n)$ 为 **钦定** $n$ 个的方案数,$g(n)$ 为 **恰好** $n$ 个的方案数。(这里 **钦定** 可以认为是固定 $n$ 个,剩下的随意。) 那么上面这个式子显然成立。 我们可以看出 $f(n)$ 的限制比 $g(n)$ 宽松,于是我们可以先通过一些手段来求出 $f(n)$,然后再来用反演求 $g(n)$。 当然有的时候反演完还要推一堆乱七八糟的式子,这个考验数学功底了。 还有,看见 **恰好,至少,至多** 这些字眼的计数题,可以考虑是不是容斥。 ## 3.具体题目 [P4859 已经没有什么好害怕的了](https://www.luogu.com.cn/problem/P4859) ### $Description:$ 给定 $n, k$ 和长度为 $n$ 的序列 $a_i, b_i$。将 $a_i, b_i$ 两两配对,使得 $a_i > b_i$ 的个数比 $b_i > a_i$ 的个数多 $k$,求有多少种配对方案。 ### $Solution:$ 易得 $a_i > b_i$ 的个数**恰好**为 $\dfrac{n+k}{2}$。 发现直接这样搞挺难的,于是想到容斥。 定义 $f(x)$ 为**钦定** $a_i > b_i$ 的个数为 $x$,$g(x)$ 为**恰好** $a_i > b_i$ 的个数为 $x$。 那么显然有 $$f(x)=\sum_{i=x}^n\binom{i}{x}g(i)$$ 二项式反演一下,可得 $$g(x)=\sum_{i=x}^n(-1)^{i-x}\binom{i}{x}f(i)$$ 我们要的就是 $g(\dfrac{n+k}{2})$。 于是只要求出 $f$ 函数就行了。 这里考虑 $\mathrm{DP}$。先对 $a, b$ 排个序,定义 $dp_{i, j}$ 为 $a_1$ 到 $a_i$ 中有 $j$ 对可配对的数。 那么就有状态转移方程: $$dp_{i, j}=dp_{i-1,j}+dp_{i-1, j-1}\times (cnt_i-j+1)$$ 其中 $cnt_i$ 为 $\sum_{j=1}^n[a_i > b_j]$。 然后 $f(x)=dp_{n, x}\times (n-x)!$。 完了。 ```cpp #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <queue> #include <vector> #include <cmath> using namespace std; #define ll long long #define fi first #define se second #define y1 y_csyakioi_1 #define y0 y_csyakioi_0 #define dingyi int mid = l + r >> 1, ls = p << 1, rs = p << 1 | 1 inline int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar(); } while(ch >= '0' && ch <= '9'){ x = x * 10 + (ch ^ 48); ch = getchar(); } return x * f; } const int N = 2010, mod = 1000000009; int n, k, a[N], b[N], fac[N], inv[N], f[N][N], F[N], G[N], cnt[N], tot, ans; inline int fpow(int x, int p){ int ans = 1; for(; p; p >>= 1, x = 1ll * x * x % mod) if(p & 1) ans = 1ll * ans * x % mod; return ans; } inline void initfac(int n){ fac[0] = 1; for(int i = 1; i <= n; ++i) fac[i] = 1ll * fac[i - 1] * i % mod; inv[n] = fpow(fac[n], mod - 2); for(int i = n - 1; ~i; --i) inv[i] = 1ll * inv[i + 1] * (i + 1) % mod; } inline int C(int n, int m){ return n < m ? 0 : 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod; } inline int pown1(int x){ return x & 1 ? mod - 1 : 1; } int main(){ n = read(); k = read(); initfac(n); for(int i = 1; i <= n; ++i) a[i] = read(); for(int i = 1; i <= n; ++i) b[i] = read(); if(n + k & 1) return puts("0") & 0; k = n + k >> 1; sort(a + 1, a + n + 1); sort(b + 1, b + n + 1); for(int i = 1; i <= n; ++i){ while(tot < n && b[tot + 1] < a[i]) ++tot; cnt[i] = tot; } f[0][0] = 1; for(int i = 1; i <= n; ++i){ f[i][0] = f[i - 1][0]; for(int j = 1; j <= i; ++j){ f[i][j] = (f[i - 1][j] + 1ll * f[i - 1][j - 1] * (cnt[i] - j + 1) % mod) % mod; } } for(int i = 0; i <= n; ++i) F[i] = 1ll * f[n][i] * fac[n - i] % mod; for(int i = k; i <= n; ++i){ ans = (ans + 1ll * pown1(i - k) * C(i, k) % mod * F[i] % mod) % mod; } printf("%d", ans); return 0; } ``` --- $To\ be\ continued......$