是我比较弱的图论题,比赛的时候没出.
传送门
题意:给定一棵树和三个集合,要求你从每个集合中任选一点,在得到三个点u,v,w后再在树上找到一个点r,使得dis(u,r)+dis(v,r)+dis(w,r)最小.
有一个结论是:这个点r一定在路径uv,vw,uw三个路径的交点上.
首先,可以确定的是,这三条路径一定会有交点,(因为是一棵树,树上的一个点到另一个点只有一条路径),当然这个交点唯一.
那么dis(u,r)+dis(v,r)+dis(w,r)=(dis(u,v)+dis(v,w)+dis(u,w))/2
有了这个结论后题目就变简单了:两个集合内各选一个点,计算出满足条件的任意点对的距离和.
三个集合两两枚举处理即可.
计算的时候有个技巧:考虑每条边对答案的贡献即可,即边的两边的属于集合a的元素和属于集合b的元素的数量统计一下即可进行运算.
#include<bits/stdc++.h>
using namespace std;
#define MAXN 200500
#define ll long long
vector<pair<int,ll>>e[MAXN];
ll cnt[MAXN];
int x[MAXN][3];
ll sz[MAXN][3];
long double ans=0;
void solve(int rt,int pre)
{
for(auto it:e[rt]) {
if (it.first == pre)continue;
int so=it.first;
solve(it.first,rt);
ll now=sz[so][0]*(sz[1][1]-sz[so][1])*cnt[2];
now+=sz[so][1]*(sz[1][0]-sz[so][0])*cnt[2];
now+=sz[so][1]*(sz[1][2]-sz[so][2])*cnt[0];
now+=sz[so][2]*(sz[1][1]-sz[so][1])*cnt[0];
now+=sz[so][2]*(sz[1][0]-sz[so][0])*cnt[1];
now+=sz[so][0]*(sz[1][2]-sz[so][2])*cnt[1];
ans+=now/2.0/(1ll*cnt[0]*cnt[1]*cnt[2])*it.second;
//注意这里一定要先除再乘,不然会超ll范围
}
}
void root(int rt,int pre)
{
for(int i=0;i<3;i++)
{
if(x[rt][i])sz[rt][i]++;
}
for(auto it:e[rt]){
if(it.first==pre)continue;
root(it.first,rt);
for(int j=0;j<3;j++)
sz[rt][j]+=sz[it.first][j];
}
}
int main()
{
int n;cin>>n;
for(int i=1;i<n;i++)
{
int u,v,w;scanf("%d%d%d",&u,&v,&w);
e[u].push_back({v,w});
e[v].push_back({u,w});
}
for(int i=1;i<=3;i++)
{
int m;
scanf("%d",&m);
cnt[i-1]=m;
for(int j=1;j<=m;j++)
{
int cur;cin>>cur;
x[cur][i-1]=1;
}
}
root(1,0);
solve(1,0);
printf("%.7Lf\n",ans);
}