数学 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}\),可以画图理解一下(确实怎么将都不如自己画图更清楚)。

于是反演回去即可。

注意特判无解!

例题与代码

P3175 [HAOI2015] 按位或

板子,不必多说。

#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;
}
posted @ 2025-04-27 21:26  porse114514  阅读(14)  评论(0)    收藏  举报