题目
题目链接:
给出一棵 (n) 个节点的树。有 (m) 个询问,每一个询问包含两个数 (a,b)。我们可以对任意两个点连一条无向边,并且使得加上这条边后 (a,b) 处在一个环内。对于每一个询问,求这样的环的期望长度。
(2leq n,mleq 10^5)。
思路
如果这两个点不互为祖孙关系,答案就是
[frac{f[y] imes siz[x]+f[x] imes siz[y]}{siz[x]siz[y]}+dis
]
否则设 (x) 是 (y) 祖先,答案是
[frac{f[y](n-siz[q])+(g[x]-f[q]-siz[q])siz[y]}{(n-siz[q])siz[y]}+dis
]
其中 (q) 是 (x) 到 (y) 链上 (x) 的儿子。
时间复杂度 (O(n))。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010,LG=18;
int n,m,tot,head[N],dep[N],siz[N],fa[N][LG+1];
ll f[N],g[N];
struct edge
{
int next,to;
}e[N*2];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
void dfs1(int x,int ff)
{
siz[x]=1; dep[x]=dep[ff]+1;
fa[x][0]=ff;
for (int i=1;i<=LG;i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=ff)
{
dfs1(v,x);
f[x]+=f[v]+siz[v];
siz[x]+=siz[v];
}
}
}
void dfs2(int x,int fa)
{
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=fa)
{
g[v]=g[x]+n-2*siz[v];
dfs2(v,x);
}
}
}
int lca(int x,int y)
{
if (dep[x]<dep[y]) swap(x,y);
for (int i=LG;i>=0;i--)
if (dep[fa[x][i]]>=dep[y]) x=fa[x][i];
if (x==y) return x;
for (int i=LG;i>=0;i--)
if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int findson(int x,int y)
{
for (int i=LG;i>=0;i--)
if (dep[fa[y][i]]>dep[x]) y=fa[y][i];
return y;
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1,x,y;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
dfs1(1,0);
g[1]=f[1];
dfs2(1,0);
while (m--)
{
int x,y;
scanf("%d%d",&x,&y);
int p=lca(x,y);
ll dis=dep[x]+dep[y]-2*dep[p]+1;
if (p==y) swap(x,y);
if (p==x)
{
int q=findson(x,y);
printf("%.12lf
",1.0*(f[y]*(n-siz[q])+(g[x]-f[q]-siz[q])*siz[y])/(n-siz[q])/siz[y]+dis);
}
else
printf("%.12lf
",1.0*(f[y]*siz[x]+f[x]*siz[y])/siz[x]/siz[y]+dis);
}
return 0;
}