题目
题目链接:
给出一棵 \(n\) 个节点的树。有 \(m\) 个询问,每一个询问包含两个数 \(a,b\)。我们可以对任意两个点连一条无向边,并且使得加上这条边后 \(a,b\) 处在一个环内。对于每一个询问,求这样的环的期望长度。
\(2\leq n,m\leq 10^5\)。
思路
如果这两个点不互为祖孙关系,答案就是
\[\frac{f[y]\times siz[x]+f[x]\times 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\n",1.0*(f[y]*(n-siz[q])+(g[x]-f[q]-siz[q])*siz[y])/(n-siz[q])/siz[y]+dis);
}
else
printf("%.12lf\n",1.0*(f[y]*siz[x]+f[x]*siz[y])/siz[x]/siz[y]+dis);
}
return 0;
}