题目描述
给定一棵有n个结点的树,Q个询问,每次询问点x到点y亮点之间的距离
输入
第一行一个n,表示有n个节。
接下来有n-1行,每行2个整数x,y表示x,y之间有一条连边。
然后一个整数Q,表示有Q次询问,接下来Q行每行2个整数x,y表示询问x到y的距离。
输出
输出Q行,每行表示每个询问的结果
样例输入
6
1 2
1 3
2 4
2 5
3 6
2
2 6
5 6
样例输出
3
4
题解
倍增法是由几个步骤配合完成的:
首先我们对输入的数据进行建树,然后对树进行深度遍历得到每个节点的深度以及记录它们的父节点。
然后我们用一个特殊的数组st[i][j]表示节点i上跳2^j次后得到的节点,其中st[i][0]表示第i个节点的父节点,有一个公式是需要理解的,st[ i ][ j ]=st[ st[i][j-1] ][ j-1 ],意思就是第i个节点上跳2^j个节点等于i节点向上跳2^(j-1)的这个节点向上跳2^(j-1)次方(2^j=2^(j-1)+2^(j-1)),j的取值范围为[1,log2 n]。
接着就是求两个节点的最近公共祖先,首先我们要把两个节点调整到同一深度,具体做法是是使深度大的上跳;当到达同一深度后同时上跳,直到上跳得到的节点相同,那么这个节点就是它们的最近公共祖先,在程序中用get_lca()来实现。
这题首先求出两个节点的lca,然后x,y两点之间的最短路径就是x->xy的最近公共祖先->y,它们的距离就等于x的深度加上y的深度减去两倍的公共祖先的深度。
关于链式前向星,它是通过保存边来保存一颗树的,首先用num_edge记录边的编号(顺序无关)。然后有一个first[i]数组,它保存的是与i点相连的已加入的最后一条边的num_edge编号(为什么这样保存后面说),然后开一个结构体Edge,next表示下一条边的num_edge编号,to表示这条边到达的点。
存储的方法是:每加入一条边(i,j),就把这条边加到i、j链表的首部,并更新first[i],first[j]数组。
广度优先遍历的方法就是,对于与i相连的所有点,找到最后一次加入的边的num_edge编号,i是边的起点(不是真正意义的起点),edge[i].to是边的终点,通过访问edge[i].next找到下一条与i相连的边的编号继续访问。
而深度优先遍历则是找到某条边的终点(不是真正意义的终点),将这个终点作为起点,继续访问它的终点循环下去。
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=4e5+10;
int num_edge=0,father[maxn],dep[maxn],st[maxn][25];
int first[maxn];//x点最后一条边的编号
struct Edge
{
int next;//下一条边的编号
int to;//这条边到达的点
//int dist;
}edge[maxn*2];
void add_edge(int from,int to)
{
edge[++num_edge].next=first[from];
edge[num_edge].to=to;
first[from]=num_edge;//将这一条边作为最后一条边,下一条边其实是上一次存储的边
}
void dfs(int x,int fa)
{
father[x]=fa;
for(int i=first[x];i!=-1;i=edge[i].next)
{
int to=edge[i].to;
if(to==fa)
continue;
dep[to]=dep[x]+1;
dfs(to,x);
}
}
int get_lca(int x,int y)
{
if(dep[x]<dep[y])
swap(x,y);
for(int i=20;i>=0;i--)
if(dep[ st[x][i] ]>=dep[y])
x=st[x][i];
if(x==y)
return x;
for(int i=20;i>=0;i--)
{
if(st[x][i]!=st[y][i])
{
x=st[x][i];
y=st[y][i];
}
}
return father[x];
}
int main()
{
int n,q;
scanf("%d",&n);
memset(first,-1,sizeof(first));
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d %d",&x,&y);
add_edge(x,y);//链式前向星
add_edge(y,x);
}
dep[1]=1;//选作为根节点
dfs(1,0);
for(int i=1;i<=n;i++)
st[i][0]=father[i];//向上跳一格就是自己的父节点
for(int j=1;j<=20;j++)
for(int i=1;i<=n;i++)
st[i][j]=st[ st[i][j-1] ][ j-1 ];
scanf("%d",&q);
while(q--)
{
int x,y;
scanf("%d %d",&x,&y);
int temp=get_lca(x,y);
printf("%d\n",dep[x]+dep[y]-2*dep[temp]);
}
return 0;
}