数学 Trick 之:二项式反演+匹配滞后
能够解决的问题
形如:
- 你需要给两个集合建立单射,并恰好有 \(k\)(给定)对满足性质 \(P\)(给定),求方案数。
优缺点
优点:代码简单,这类题有区分度。
缺点:泛用性不太广。
思路
- 考虑反演:放宽性质,将恰好变为至少。
- dp 转移。
在转移中,有一个非常重要的技巧:匹配滞后。具体来说,有这两种转移方式(\(dp_{i, j}\) 表示前 \(i\) 和,匹配了 \(j\) 组了):
- 什么都不做,直接沿用上一个状态,\(dp_{i, j} \gets dp_{i - 1, j}\)(你一定会感到疑惑,请先往下看)。
- 匹配一组满足题目要求的性质的(具体见前文“能够解决的问题”),\(dp_{i, j} \gets dp_{i - 1, j - 1} + k\)(\(k\) 为能够和 \(i\) 匹配的元素的个数)。
聪明的你,一定看出了端倪:
- 在你做第一类转移时,这个 \(i\) 什么也没有匹配呀!
- 你这样怎么保证答案的正确性呢?
我们在转移中,只匹配了满足性质的,又因为我们将将恰好变为了至少,所以其他的随便匹配就行(Trick 核心,请深刻理解)。
所以我们只需要做后一步操作:
\[dp_{i, j} \gets dp_{i, j} \times (i - j)!
\]
我们就得到了答案,然后反演回去即可。
例题与代码
这道题是这个 Trick 的典型应用,将题目中的 \(k\) 变成 \(\frac{n + k}{2}\) 即为这题的模板,照着思路做即可。
#include <bits/stdc++.h>
using namespace std;
#define int __int128
constexpr int maxn = 2010, modd = 1000000009;
int n, a[maxn], b[maxn], r[maxn], k, dp[maxn][maxn], fac[maxn], ans, c[maxn][maxn];
int p(int k) { // 反演系数
return ((k & 1) ? -1 : 1);
}
int read() {
int x = 0;
char ch = getchar();
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + ch - 48, ch = getchar();
return x;
}
void write(int x) {
if (x < 0) putchar('-'), x = -x;
if (x < 10) {
putchar(x + 48);
} else {
write(x / 10);
putchar(x % 10 + 48);
}
return ;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
n = read();
k = read();
k = (n + k) >> 1;
c[0][0] = 1;
for (int i = 1; i <= n; i++) { // 组合数初始化
c[i][0] = c[i][i] = 1;
for (int j = 1; j < i; j++) {
c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % modd;
}
}
fac[0] = 1;
for (int i = 1; i <= n; i++) { // 阶乘初始化
fac[i] = i * fac[i - 1] % modd;
a[i] = read();;
}
for (int i = 1; i <= n; i++) {
b[i] = read();
}
sort(a + 1, a + 1 + n);
sort(b + 1, b + 1 + n);
for (int i = 1, j; i <= n; i++) { // r[i] 表示有多少个 b[i] 能与 a[i] 产生有贡献的匹配
j = 0;
while (a[i] > b[j + 1] && j < n) j++;
r[i] = j;
}
dp[0][0] = 1;
for (int i = 1; i <= n; i++) { // dp
dp[i][0] = 1;
for (int j = 1; j <= i; j++) {
dp[i][j] = dp[i - 1][j] + (r[i] - j + 1) * dp[i - 1][j - 1] % modd;
dp[i][j] %= modd;
}
}
for (int i = 0; i <= n; i++) { // 将剩下的匹配
dp[n][i] = dp[n][i] * fac[n - i] % modd;
}
for (int i = k; i <= n; i++) { // 反演
ans = (ans + p(i - k) * c[i][k] * dp[n][i] % modd) % modd;
}
write((ans % modd + modd) % modd);
return 0;
}

浙公网安备 33010602011771号