CF 804D Expected diameter of a tree

题目描述

https://www.luogu.com.cn/problem/CF804D

简要题意:给定一个森林和 $q$ 个询问,每次询问给定一个无序二元组 $(x,y)$,问将 $x$ 和 $y$ 所在的树随机连接起来后的直径的期望

$n,q\le 10^5$

Solution

首先我们对于每棵树求出树上从所有点出发的最长路径,这个有经典的 $O(n)$ 做法

那么答案即为 $\frac{1}{nm}\sum_{i=1}^n\sum_{j=1}^m max\lbrace f_{1,i}+f_{2,j},d_1,d_2\rbrace$

注意到询问满足对二元组的自然根号

所以我们枚举较小的树上的点,然后二分即可

时间复杂度 $O(n\sqrt n\log n)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <map>
#include <iomanip>
#define maxn 100010
#define ll long long
using namespace std;

int n, m, q;

struct Edge {
int to, next;
} e[maxn * 2]; int c1, head[maxn];
inline void add_edge(int u, int v) {
e[c1].to = v; e[c1].next = head[u]; head[u] = c1++;
}

int fa[maxn];
void init_fa(int n) { for (int i = 1; i <= n; ++i) fa[i] = i; }

int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }

inline void merge(int x, int y) {
int fx = find(x), fy = find(y);
if (fx == fy) return ;
fa[fx] = fy;
}

int f[maxn], g[maxn], fr[maxn];
void dfs(int u, int fa) {
f[u] = g[u] = -1;
for (int i = head[u]; ~i; i = e[i].next) {
int v = e[i].to, w = 1; if (v == fa) continue;
dfs(v, u);
if (f[u] < f[v] + w) g[u] = f[u], f[u] = f[v] + w, fr[u] = v;
else if (g[u] < f[v] + w) g[u] = f[v] + w;
}
if (f[u] < 0) f[u] = 0, fr[u] = u;
else if (g[u] < 0) g[u] = 0;
}

void Dfs(int u, int fa) {
for (int i = head[u]; ~i; i = e[i].next) {
int v = e[i].to, w = 1, t; if (v == fa) continue;
if (fr[u] == v) t = g[u] + w;
else t = f[u] + w;
if (f[v] < t) g[v] = f[v], f[v] = t, fr[v] = u;
else if (g[v] < t) g[v] = t;
Dfs(v, u);
}
}

vector<int> A[maxn];
vector<int> suf[maxn];
map<pair<int, int>, double> mp;
double query(int x, int y) {
if (mp.find({ x, y }) != mp.end()) return mp[{ x, y }];
double ans = 0; int mx = max(A[x].back(), A[y].back());
ll s = 0, t, sum = 1ll * A[x].size() * A[y].size();
for (auto u : A[x]) {
auto it = lower_bound(A[y].begin(), A[y].end(), mx - u - 1);
if (it == A[y].end()) continue;
t = A[y].end() - it;
s += t; ans += (u + 1) * t + suf[y][it - A[y].begin()];
}
ans += (sum - s) * mx;
return mp[{ x, y }] = ans / sum;
}

int main() { fill(head, head + maxn, -1);
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);

cin >> n >> m >> q; init_fa(n);
for (int i = 1; i <= m; ++i) {
int x, y; cin >> x >> y;
add_edge(x, y); add_edge(y, x);
merge(x, y);
}
for (int i = 1; i <= n; ++i) if (find(i) == i) dfs(i, 0);
for (int i = 1; i <= n; ++i) if (find(i) == i) Dfs(i, 0);
for (int i = 1; i <= n; ++i) A[find(i)].push_back(f[i]);
for (int i = 1; i <= n; ++i)
if (find(i) == i) {
sort(A[i].begin(), A[i].end());
int sz = A[i].size();
suf[i].resize(sz); suf[i][sz - 1] = A[i][sz - 1];
for (int j = sz - 2; ~j; --j)
suf[i][j] = suf[i][j + 1] + A[i][j];
}
for (int i = 1; i <= q; ++i) {
int x, y; cin >> x >> y;
x = find(x); y = find(y);
if (x == y) { cout << "-1\n"; continue; }
if (A[x].size() > A[y].size() || A[x].size() == A[y].size() && x > y) swap(x, y);
cout << fixed << setprecision(7) << query(x, y) << "\n";
}
return 0;
}