题解:CF629E Famil Door and Roads
思路
先考虑一共有多少种在环上的情况。
-
钦定 \(x\) 的深度小于 \(y\)。询问的 \(x,y\) 满足 \(x\) 是 \(y\) 的祖先,也就是 \(y\) 在以 \(x\) 为根的子树中。
首先从 \(x\) 到 \(y\) 的路径上(除 \(x,y\) 外)的点包括这些点的子树都不用考虑,这样必然成不了环 。

那么这棵树就被分成了两部分,设 \(siz_i\) 为以 \(i\) 为根的子树大小。我们需要找到 \(x\) 的某一个儿子 \(z\) 满足以 \(z\) 为根的子树内包含 \(y\)。那么上图左部分就有 \(n-siz_z\) 个点,右部分有 \(siz_y\) 个点,那么就有 \((n-siz_z)\times siz_y\) 种情况成环。
-
询问的 \(x,y\) 没有上述的祖孙关系,形如这样

很显然,这种情况的成环的情况数为 \(siz_x\times siz_y\)。
那么好,现在情况数分析完了,我们再想怎么计算答案。
对于情况 \(1\),设左半部分所有点到 \(x\) 的距离和为 \(sum_x\),每个点都会和右边的所有点连一次,对环长的总贡献为 \(sum \times siz_y\),设右半部分所有点到 \(y\) 的距离和为 \(sum_y\),同理可得它对环长的贡献为 \((n-siz_z)\times sum_y\),而所有可能的环必然包含 \(x,y\) 之间的路径,设路径长为 \(dis\),那么贡献为 \((dis+1)\times (n-siz_z)\times siz_y\),累加起来除情况数即可。
而对于情况 \(2\),与 \(1\) 同理,环的总长为 \(siz_x\times sum_y+siz_y\times sum_x+siz_x\times siz_y\times dis\)。
上面两种情况,对于 \(1\) 中的 \(sum_y\) 和 \(2\) 中的 \(sum_x,sum_y\) 很好求,定义 \(g_i\) 为以 \(i\) 为根的子树到 \(i\) 的距离和,则 \(g_i=\sum_{j\in son_i}g_j+siz_j\),即求 \(g_x\) 和 \(g_y\)。
但对于 \(1\) 中的 \(sum_x\) 却并不好求。观察 \(1\) 图发现,\(sum_x\) 实际上是将所有点到 \(x\) 的距离和减掉了儿子 \(z\) 这棵子树的贡献。那么定义 \(f_i\) 为全局所有点到 \(i\) 的距离和,这是很典的换根 dp。那么 \(sum_x=f_x-g_z-siz_z\)。还有一个问题,就是 \(1\) 中 \(z\) 的求法,这里很显然可以树上倍增。
那么综上来看,复杂度为 \(O(n+m\log n)\)。
code
int dep[N],fa[N][21],siz[N];
int f[N],g[N],n,m,rt=1;
vector<int>G[N];
void dfs(int now,int from)
{
siz[now]=1;
for(int i=1;(1<<i)<=dep[now];i++)
fa[now][i]=fa[fa[now][i-1]][i-1];
g[now]=0;
for(int to:G[now])
{
if(to==from) continue;
fa[to][0]=now;dep[to]=dep[now]+1;
dfs(to,now);
g[now]+=g[to]+siz[to],siz[now]+=siz[to];
}
return;
}
void redfs(int now,int from)
{
for(int to:G[now])
{
if(to==from) continue;
f[to]=f[now]+(n-2*siz[to]);
redfs(to,now);
}
return;
}
int LCA(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;~i;i--)
if((1<<i)<=dep[x]-dep[y]) x=fa[x][i];
if(x==y) return x;
for(int i=20;~i;i--)
if((fa[x][i]^fa[y][i])) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
signed main()
{
read(n,m);
for(int i=1,x,y;i<n;i++)
{
read(x,y);
G[x].push_back(y);
G[y].push_back(x);
}
dfs(rt,0);for(int i=1;i<=n;i++) f[rt]+=dep[i];redfs(rt,0);
for(int i=1,x,y,z;i<=m;i++)
{
read(x,y);
if(dep[x]>dep[y]) swap(x,y);
z=LCA(x,y);
if(z==x){//情况 1
int dis=dep[x]+dep[y]-2*dep[z],son=y;
for(int i=20;~i;i--)
if(fa[son][i]&&dep[fa[son][i]]>dep[x]) son=fa[son][i];//倍增求 z
double Ans=siz[y]*(f[x]-g[son]-siz[son])+(n-siz[son])*g[y]+(dis+1)*(n-siz[son])*siz[y];
printf("%.6lf\n",Ans/((n-siz[son])*siz[y]));
}
else{//情况 2
int dis=dep[x]+dep[y]-2*dep[z];
double Ans=siz[x]*siz[y]*(dis+1)+siz[y]*g[x]+siz[x]*g[y];
printf("%.6lf\n",Ans/(siz[x]*siz[y]));
}
}
return 0;
}

浙公网安备 33010602011771号