传送门:https://codeforces.ml/gym/102798/problem/C
题意
给
一
棵
树
,
树
上
的
点
分
为
三
种
。
给一棵树,树上的点分为三种。
给一棵树,树上的点分为三种。
首
先
分
别
在
三
种
点
中
随
机
选
择
一
点
a
,
b
,
c
,
然
后
找
到
一
个
合
适
的
v
,
使
得
首先分别在三种点中随机选择一点a,b,c,然后找到一个合适的v,使得
首先分别在三种点中随机选择一点a,b,c,然后找到一个合适的v,使得
f
=
m
i
n
(
d
i
s
(
a
,
v
)
+
d
i
s
(
b
,
v
)
+
d
i
s
(
c
,
v
)
)
f=min(dis(a,v)+dis(b,v)+dis(c,v))
f=min(dis(a,v)+dis(b,v)+dis(c,v))
求 出 f 的 期 望 。 求出f的期望。 求出f的期望。
思路
当 a , b , c 确 定 下 来 , 那 么 v 也 会 确 定 下 来 , 因 为 m i n , 所 以 我 们 围 绕 a , b , c 展 开 。 当a,b,c确定下来,那么v也会确定下来,因为min,所以我们围绕a,b,c展开。 当a,b,c确定下来,那么v也会确定下来,因为min,所以我们围绕a,b,c展开。
数
学
上
可
以
推
出
m
i
n
(
d
i
s
(
a
,
v
)
+
d
i
s
(
b
,
v
)
+
d
i
s
(
c
,
v
)
)
=
1
2
(
d
i
s
(
a
,
b
)
+
d
i
s
(
b
,
c
)
+
d
i
s
(
c
,
a
)
)
数学上可以推出min(dis(a,v)+dis(b,v)+dis(c,v))=\frac{1}{2}(dis(a,b)+dis(b,c)+dis(c,a))
数学上可以推出min(dis(a,v)+dis(b,v)+dis(c,v))=21(dis(a,b)+dis(b,c)+dis(c,a))
这
样
我
们
就
不
需
要
考
虑
v
的
存
在
,
而
怎
么
求
d
i
s
呢
?
这样我们就不需要考虑v的存在,而怎么求dis呢?
这样我们就不需要考虑v的存在,而怎么求dis呢?
显 然 d i s 中 的 点 是 在 树 上 的 , 所 以 总 贡 献 为 ∑ x ∈ a ∑ y ∈ b d i s ( x , y ) ∣ a ∣ ∣ b ∣ + ∑ x ∈ c ∑ y ∈ b d i s ( x , y ) ∣ c ∣ ∣ b ∣ + ∑ x ∈ a ∑ y ∈ c d i s ( x , y ) ∣ a ∣ ∣ c ∣ 显然dis中的点是在树上的,所以总贡献为\frac{\sum_{x\in a}\sum_{y\in b}dis(x,y)}{|a||b|}+\frac{\sum_{x\in c}\sum_{y\in b}dis(x,y)}{|c||b|}+\frac{\sum_{x\in a}\sum_{y\in c}dis(x,y)}{|a||c|} 显然dis中的点是在树上的,所以总贡献为∣a∣∣b∣∑x∈a∑y∈bdis(x,y)+∣c∣∣b∣∑x∈c∑y∈bdis(x,y)+∣a∣∣c∣∑x∈a∑y∈cdis(x,y)
a , b , c 的 个 数 输 入 的 时 候 就 知 道 了 , 那 么 就 是 求 所 有 的 d i s , 即 树 上 任 意 两 点 距 离 和 。 a,b,c的个数输入的时候就知道了,那么就是求所有的dis,即树上任意两点距离和。 a,b,c的个数输入的时候就知道了,那么就是求所有的dis,即树上任意两点距离和。
很 多 人 都 知 道 , 树 上 任 意 两 点 之 间 距 离 就 是 d f s 一 遍 树 , 对 于 一 条 边 a n s + = ( n − s i z [ v ] ) ∗ s i z [ v ] ∗ w 很多人都知道,树上任意两点之间距离就是dfs一遍树,对于一条边ans+=(n-siz[v])*siz[v]*w 很多人都知道,树上任意两点之间距离就是dfs一遍树,对于一条边ans+=(n−siz[v])∗siz[v]∗w
这
里
同
如
此
,
只
不
过
是
进
化
版
,
是
要
在
三
个
块
中
选
择
不
同
点
求
任
意
距
离
和
。
这里同如此,只不过是进化版,是要在三个块中选择不同点求任意距离和。
这里同如此,只不过是进化版,是要在三个块中选择不同点求任意距离和。
没
关
系
,
设
s
i
z
[
u
]
[
1
/
2
/
3
]
表
示
以
u
为
根
节
点
的
子
树
中
,
1
/
2
/
3
种
类
的
点
有
多
少
个
。
没关系,设siz[u][1/2/3]表示以u为根节点的子树中,1/2/3种类的点有多少个。
没关系,设siz[u][1/2/3]表示以u为根节点的子树中,1/2/3种类的点有多少个。
然 后 根 据 s i z 大 小 循 环 转 移 即 可 。 然后根据siz大小循环转移即可。 然后根据siz大小循环转移即可。
难 点 应 该 就 是 怎 么 在 不 同 块 中 求 贡 献 了 。 首 先 d f s 1 预 处 理 出 全 部 的 s i z , 再 d f s 2 对 边 求 贡 献 。 难点应该就是怎么在不同块中求贡献了。首先dfs1预处理出全部的siz,再dfs2对边求贡献。 难点应该就是怎么在不同块中求贡献了。首先dfs1预处理出全部的siz,再dfs2对边求贡献。
Code
#include "bits/stdc++.h"
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
struct Edge {
int v;
ll w;
};
vector<Edge> g[N];
ll cnt[4], siz[N][4];;
double ans;
void dfs1(int u, int fa) {
for(auto e : g[u]) {
int v = e.v;
if(v == fa) continue;
dfs1(v, u);
for(int i = 1;i <= 3; i++) {
siz[u][i] += siz[v][i];
}
}
}
void dfs2(int u, int fa) {
for(auto e : g[u]) {
int v = e.v;
if(v == fa) continue;
dfs2(v, u);
for(int i = 1;i <= 3; i++) {
for(int j = 1;j <= 3; j++) {
if(i == j) continue;
ans += 1.0 * ((siz[1][i] - siz[v][i]) * siz[v][j] * 1.0 * e.w / (cnt[i] * cnt[j]) / 2.0);
}
}
}
}
void solve() {
int n; cin >> n;
for(int i = 1;i < n; i++) {
int u, v; ll w; cin >> u >> v >> w;
g[u].push_back(Edge{v, w});
g[v].push_back(Edge{u, w});
}
for(int i = 1;i <= 3; i++) {
cin >> cnt[i];
for(int j = 1;j <= cnt[i]; j++) {
int x; cin >> x;
siz[x][i]++;
}
}
dfs1(1, -1);
dfs2(1, -1);
cout << fixed << setprecision(12) << ans << endl;
}
signed main() {
solve();
}