题目链接:https://loj.ac/problem/10130
分析:
树上两点(x,y)之间的距离为:x的深度+y的深度-LCA(x,y)的深度。
理论:
LCA:最近公共祖先
暴力:
(1)先DFS一遍找出每个点的DEP(深度)。
(2)深度大的顶点往深度小的顶点跳,跳到深度相同。
(3)如果不是同一个点,两个点继续向上跳。
优化(树上倍增法):
f[x,k]表示x的2^k辈祖先,即从x向根节点走 2^k步到达的结点。如果结点不存在,则令f[x,k]=0.
f[x,0]就是x的父亲结点。
x向根节点走2^k步《=》向根走 2^(k-1),再走 2^(k-1)。所以对于k属于[1,logn],有f[x][k]=f[f[x][k-1]][k-1]。
将f数组预处理出来,在计算LCA
1.设dep[x]表示x的深度。不妨设dep[x]>=dep[y],否则交换x和y。
2.用二进制拆分的思想,将x向上调整到和y相同的深度。
代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int fir[N],net[2*N],to[2*N],num;
int f[N][21],dep[N];
void Index(int x,int y)
{
to[++num]=y;
net[num]=fir[x];
fir[x]=num;
}
void Deal_first(int u,int father)/*预处理出每个结点的深度以及每个结点走2^0 2^1 2^2...步到达的结点*/
{
dep[u]=dep[father]+1;
for(int i=0; i<=19; i++)
f[u][i+1]=f[f[u][i]][i];
for(int i=fir[u]; i; i=net[i])
{
int v=to[i];
if(v==father)/*因为无向边,所以会出现v=father的情况*/
continue;
f[v][0]=u;
Deal_first(v,u);
}
}
int LCA(int x,int y)
{
if(dep[x]<dep[y])
swap(x,y);
for(int i=20; i>=0; i--)
{
if(dep[f[x][i]]>=dep[y])
x=f[x][i];
if(x==y) return x;
}
for(int i=20; i>=0; i--)
{
if(f[x][i]!=f[y][i])/*刚开始可能跳到结点不存在的位置,f[x][i]=f[y][i]=0,不执行if选择语句*/
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1,x,y; i<=n-1; i++)
{
scanf("%d%d",&x,&y);
Index(x,y);
Index(y,x);
}
Deal_first(1,0);
int q;
scanf("%d",&q);
while(q--)
{
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",dep[x]+dep[y]-2*dep[LCA(x,y)]);
}
return 0;
}