二项式反演
Contents
介绍
二项式反演是一种特殊的容斥,用来解决 “恰好选 $k$ 个的方案有多少种” 的问题。
一般,我们可以求出 “至多/至少选 $k$ 个的方案有多少种” 的问题,由此可以得出恰好选 $k$ 个有多少种。
公式
设 $g_k$ 表示 至多 选 $k$ 个的方案数,$f_k$ 表示恰好选 $k$ 个的方案数。
$$f_k = \sum\limits_{i=0}^k (-1)^{k-i}C_k^i g_i$$
设 $g_k$ 表示 至少 选 $k$ 个的方案数,$f_k$ 表示恰好选 $k$ 个的方案数。
$$f_k = \sum\limits_{i=k}^n (-1)^{i-k}C_i^k g_i$$
其中,$n$ 是物品的总数量。
证明见这里。
例题
例1 错排问题
题意
求长度为 $n$ 的错排 permutation 的数量。
一个permutation 是错排permutation,当且仅当它不存在 $i$ 使得 $p_i=i$。
题解
求出 $g_i$ 代表最多有 $i$ 个 fixed point的数量,就有 $g_i = C_n^i (n-i)!$。
然后让 $f_i$ 为恰好有 $i$ 个 fixed point的数量。
求 $f_0$ 即可,复杂度为 $O(n)$。
例2 CF1342E
题意
给定一个 $n \times n$ 的国际象棋棋盘,要放 $n$ 个车在棋盘上,使得:
- 每个点都被覆盖到。
- 恰好有 $k$ 对车互相攻击(两个车互相攻击当且仅当两个车能直接攻击,比如中间隔了一个车就不算)。
求方案数,对 998244353 取模。
其中,$1 \leq n \leq 200000, 0 \leq k \leq \frac{n(n-1)}{2}$。
题解
首先,每个点都要被覆盖到说明要么每一行都有车,要么每一列都有车,并且这两种不能同时存在,除非 $k=0$。
所以我们假设每一行都有车,答案 $*2$ 即可(因为棋盘翻转一下就是每一列都有车了)。
现在每一行都有车了,可以发现要恰好 $k$ 对车互相攻击,我们需要恰好把这些车放进 $n-k$ 列。
但这样还是不好算,所以考虑二项式反演。
设 $g(i)$ 为:至少有 $i$ 个冲突(也就是至多放进 $n-i$ 列)中的方案数。
则 $g(i) = C_{n}^{n-i} * (n-i)^n$。
解释:先从 $n$ 列里面选择 $n-i$ 列,得到 $C_{n}^{n-i}$,然后每一行对应一个车,这个车可以放进选择的 $n-i$ 列里的任何一个,所以是 $(n-i)^n$。
然后根据
$$f_k = \sum\limits_{i=k}^n (-1)^{i-k}C_i^k g_i$$
容斥即可,答案为 $f_k*2$。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
Z fac[maxn], ifac[maxn];
Z C(int a, int b) {
if (a < b) return 0;
return fac[a] * ifac[b] * ifac[a-b];
}
int n;
ll k;
Z g(int i) {
return C(n, n-i) * qpow(Z(n-i), n);
}
int main() {
cin >> n >> k;
k %= mod;
fac[0] = ifac[0] = 1;
for (int i = 1; i <= 2e5; i++) fac[i] = fac[i-1] * i;
ifac[maxn-5] = 1/fac[maxn-5];
for (int i = 2e5-1; i >= 1; i--) ifac[i] = ifac[i+1] * (i+1);
if (k == 0) {
cout << fac[n] << endl;
return 0;
}
Z ans = 0;
for (int i = k; i <= n; i++) {
ans = ans + (((i-k)&1) ? -1 : 1) * C(i, k) * g(i);
}
cout << ans*2 << endl;
}