CF123E maze 题解

发布时间 2023-03-22 21:10:57作者: _maze

思考暴力:枚举起点和终点,再枚举每一种遍历序列得到答案。复杂度起飞。

根据期望的可加性,我们无需硬着头皮统计每一条序列的贡献,而是把序列的贡献拆成遍历序列包含的边的贡献。换句话说,假如 \(Edge\) 为遍历时经过的边集,\(e\) 为边,则:

\[E[Edge] = \sum_{e\in Edge} E[e] \]

以上为第一部分,我们将路径贡献通过期望的性质拆成了更好计算的边的贡献。


考虑边的贡献:在固定起点与终点的情况下,我们把边分为三种:在起点到终点必经之路(最短路径)上的,不在必经之路上的,与不可能走到的(这些边在以起点为根时终点的子树内)。

第一种边贡献为 \(1\)。因为我们必须经过这条边,且因为遍历方式为深度优先搜索,一定会在经过这条边后搜到终点结束,不可能再走第二次。

现在我们思考第二种边的贡献。我们设起点为根节点,并设这条边深度较浅的点为 \(u\)。我们让 \(u\) 往上跳,直到遇到一个必经之路上的点 \(fa\)。我们令这条必经之路上边的下一个点为 \(w\)

显然,\(u\)\(w\) 分属 \(fa\) 的不同子树。假如我们先进入了 \(u\) 的子树,由于终点不在其中,所以会全遍历一遍后回溯,此时这条边贡献为 \(2\),因为进去和出来按照题目要求要算两次。

假如我们先进入了 \(w\),就不会再出来,而是遍历到终点后结束答案。此时这条边不会被计算到,贡献为 \(0\)

\(u\)\(w\) 先进哪棵子树完全取决于它们在遍历序列中的先后。由于序列随机,所以概率各为 \(50\%\)

这时我们可以发现,第二种边对期望的贡献同样为 \(1\)。也就是说,固定起点终点,假如第三种边有 \(m\) 条,那么我们要求的期望就是 \(n - 1 - m\)

以上为第二部分,我们将边的贡献分类讨论,最终简化了要求的问题。


那么我们来思考怎么快速求出最终答案。对于一个点 \(u\),我们思考他作为终点的情况(此时我们设 \(1\) 为根)。对于他的每棵子树,我们求出其内所有节点作为起点的概率和 \(f_v\),那么以这些点作为起点能够走到的边数就是子树大小(因为包括了连向 \(u\) 的边)。所以贡献为 \(u\times f_v\times si_v\)

当然还有不是他子树作为起点的路径,这些路径的概率和为 \(1-f_u\),边集大小为 \(1-si_u\),按照上面的方式也能简单计算。

当然,钦定 \(u\) 作为起点运算也是可以的。以上为第三部分。


顺便一提这道题是我见过的少有的卡关闭同步流 cin 的题。请注意使用更快的输入方式。

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;

int n;
vector<int> e[N];
double x[N], y[N], sumX, sumY;
int si[N];
double f[N];
double ans;
double now;

void dfs(int u, int fa) {
	si[u] = 1;
	for (auto v : e[u]) {
		if (v == fa) continue;
		dfs(v, u);
		f[u] += f[v];
		si[u] += si[v];
	}
	f[u] += x[u];
}
signed main() {
  // freopen("text.in", "r", stdin);
	scanf("%d", &n);
	int u, v;
	for (int i = 1; i < n; ++i) {
		scanf("%d%d", &u, &v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	for (int i = 1; i <= n; ++i) {
		scanf("%d%d", &x[i], &y[i]);
		sumX += x[i], sumY += y[i];
	}

	for (int i = 1; i <= n; ++i) x[i] /= sumX;
	for (int i = 1; i <= n; ++i) y[i] /= sumY;

	dfs(1, -1);

	double ans=0;
	for (int now = 1; now <= n; ++now) {
		for (auto v : e[now]) {
			if (si[v] > si[now]) continue;
			ans += f[v] * y[now] * si[v];
		}
		ans += (1 - f[now]) * y[now] * (n - si[now]);
	}

	printf("%.10lf\n", ans);
	
	return 0;
}