[题解]Gym102798-C Rencontre (CCPC2020威海站)

题意

给出一棵\(n\)个点的带权无根树, 给出三个点的集合. 在每个集合任取一点, 在树上寻找新的一个点使得该点到三点的路径和最短. 求路径和期望

$ 1 n ^5,1 w $

题解

首先考虑这样一个问题: 给出树上三点\(a,b,c\), 求这三点汇聚到一点\(v\)的最短路径(设为\(f(a,b,c)\)) \[ f(a,b,c) = \min\{v\in V |dis(a,v) + dis(b,v) + dis(c,v)\} \]

image-20201119121551047

如上图所示, \(a,b,c\)\(3,4,5\). 观察易得汇合点\(v\)\(2\), 即\(a,b,c\)的LCA中深度最深的那个. 此时\(a,b\space a,c \space b,c\)间的每条边都恰好经过一次, 即: \[ f(a,b,c) = \frac{1}{2}(dis(a,b) + dis(a,c) + dis(b,c)) \] 由期望的性质\(E(X + Y) = E(X) + E(Y)\), 我们可以分别计算两个集合\(j,k\)中各点的距离和. 通过一遍DFS来计算子树\(u\)中属于集合\(i\)的节点个数\(num[u][i]\), 再对每条边分别统计贡献\(g\). 设\(cnt_i\)为集合\(i\)的大小. \[ g(e_i,j,k) = ((cnt_i - num[u][j])*num[u][k] + (cnt_k - num[u][k])*cnt_j)*e_i.w \] 答案即为 \[ \sum_{j = 1}^3 \sum_{k = j+1}^3\frac{\sum_{i = 1}^{n-1}g(e_i,j,k)}{2\cdot cnt_j \cdot cnt_k} \]

代码

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
//C
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
#define int long long
#define endl '\n'
const int maxn = 2e5+10;
const int maxm = 4e5+10;
int head[maxn],cnt,use[maxn][4],num[maxn][4];
int n;
ll sum[4][4];
struct Edge{
int f,t,w,next;
Edge(int f = 0,int t = 0,int w = 0,int next = 0):f(f),t(t),w(w),next(next){}
}edge[maxm];
void addedge(int f,int t,int w){
edge[cnt] = Edge(f,t,w,head[f]);
head[f] = cnt++;
}
void DFS(int u,int fa){
for(int i = 1;i <= 3;i++)if(use[u][i])num[u][i] = 1;
for(int i = head[u];~i;i = edge[i].next){
int v = edge[i].t,w = edge[i].w;
if(v == fa)continue;
DFS(v,u);
for(int j = 1;j <= 3;j++){
for(int k = j+1;k <= 3;k++){
sum[j][k] += (num[0][j] - num[v][j])*num[v][k]*w;
sum[j][k] += (num[0][k] - num[v][k])*num[v][j]*w;
}
}
for(int j = 1;j <= 3;j++)num[u][j] += num[v][j];

}
}
signed main(){
cout.setf(ios::fixed,ios::floatfield);
cout.precision(10);
ios::sync_with_stdio(0);
cin.tie(0);
memset(head,-1,sizeof(head));
cin >> n;
for(int i = 1;i < n;i++){
int f,t,w;
cin >> f >> t >> w;
addedge(f,t,w);
addedge(t,f,w);
}
for(int i = 1;i <= 3;i++){
int m;
cin >> m;
num[0][i] = m;
for(int j = 1;j <= m;j++){
int x;
cin >> x;
use[x][i] = 1;
}
}
DFS(1,-1);
ld ans = 0;
for(int i = 1;i <= 3;i++){
for(int j = i+1;j <= 3;j++)ans += (double)sum[i][j]/num[0][i]/num[0][j]/2;
}
cout << ans << endl;
return 0;
}

PS: 当时我这题题意转述的时候锅了, 导致队友直接一个点分治敲上去, 敲完才发现不对劲=-= 不然这题是有可能做出来的