数学 Trick 之:FMT + min-max 反演
能够解决的问题
- 给定 \(n\) 个物品。
- 每次随机取一个集合(显然,有 \(2^n\) 种),给定每个集合选中的概率,问期望过多久就能使所有物品都被选过一次。
优缺点
优点:代码极其简单,长度比肩 \(\text{Floyd}\)。
缺点:无。
小证明(纯显摆,但是不会的还是建议看一下)
一个事件有 \(p\) 的概率发生,那么期望多少次才能发生呢?
大家都知道是 \(\frac{1}{p}\),但是相信很多人都不会证明,这里写一下。
我们发现可以这样计算期望(设它为 \(E\)):
\[E = \sum_{i = 1}^{+\infty}i(1 - p)^{i - 1}p
\]
也就是说,前 \(i - 1\) 次都没中(概率为 \(1 - p\)),第 \(i\) 次中了(概率为 \(p\))。
则:
\[E = p \sum_{i = 1}^{+\infty} i (1 - p)^{i - 1}
\]
令 \(1 - p = t\),则:
\[E = (1 - t) \sum_{i = 1}^{+\infty} i \times t^{i - 1} \\
= (1 - t) \sum_{i = 0}^{+\infty} (i + 1)t^i
\]
令 \(A(t) = \sum_{i = 0}^{+\infty} (i + 1)t^i\)(生成函数),两边积分,得:
\[\int A(t) \; dt = \sum_{i = 0}^{+\infty}t^{i + 1} \\
= \sum_{i = 1}^{+\infty}t^{i}
\]
令其为 \(B\),则:
\[(1 - t)B = t, \\
B = \frac{t}{1 - t}
\]
所以:
\[\int A(t) \; dt = \frac{t}{1 - t}, \\
A(t) = \frac{d\frac{t}{1 - t}}{dt} = \frac{t}{(t - 1)^2} - \frac{1}{t - 1} = \frac{1}{(t - 1)^2}
\]
所以:
\[E = (1 - t) \frac{1}{(t - 1)^2} = \frac{1}{1 - t} = \frac{1}{p},
\]
得证。
思路
考虑这句话:
期望过多久就能使所有物品都被选过一次
我们可以将它变成 \(\max\{每个物品第一次被选中的期望时间\}\)。
欸,期望?\(\max\)?\(\text{min-max}\) 反演启动!
令 \(a_S\) 为集合 \(S\) 中的物品第一次被选到的期望时间,根据前面 小证明 章节可以知道,只需要知道集合 \(S\) 中的物品被第一次被选到的概率再取倒数即可。
而这,就交给 —— FMT!
不用去搜其他博客,他其实就是这四行代码:
for (int d = 1; d < maxS; d <<= 1) {
for (int S = 0; S < maxS; S += (d << 1)) {
for (int i = S; i < S + d; i++) {
a[i + d] += a[i];
}
}
}
一个基于 \(2\) 进制的 \(\text{dp}\),可以画图理解一下(确实怎么将都不如自己画图更清楚)。
于是反演回去即可。
注意特判无解!
例题与代码
板子,不必多说。
#include <bits/stdc++.h>
using namespace std;
constexpr int maxn = 1048586;
constexpr double eps = 1e-10;
int n, maxS, sz[maxn];
double a[maxn], minn[maxn], ans;
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
cin >> n;
maxS = (1 << n);
for (int i = 0; i < maxS; i++) {
cin >> a[i];
sz[i] = sz[i >> 1] + (i & 1); // 集合大小
}
for (int d = 1; d < maxS; d <<= 1) { // FMT
for (int S = 0; S < maxS; S += (d << 1)) {
for (int i = S; i < S + d; i++) {
a[i + d] += a[i];
}
}
}
for (int S = 1; S < maxS; S++) {
if (1.0 - a[S ^ (maxS - 1)] < eps) { // 注意特判无解情况
cout << "INF\n";
return 0;
}
if (sz[S] % 2) { // 反演
ans += 1.0 / (1.0 - a[S ^ (maxS - 1)]);
} else {
ans -= 1.0 / (1.0 - a[S ^ (maxS - 1)]);
}
}
printf("%.8lf\n", ans);
return 0;
}

浙公网安备 33010602011771号