这个视频讲的很清楚:链接
这里用无向链式前向星来存放这个多叉树结构,这样做要注意再dfs的时候注意特判从儿子不能回到父亲节点,不这样做则会陷入递归死循环。
下面是代码:
#include<iostream>
using namespace std;
//-----------------------------链式前向星的准备工作-----------------------------------
struct edge{
int next;//上个以这条边为起点的边在存放边的数组中的位置(方便遍历)
int to;//终点
int w;//边权
}dat[1000005];//存放边的结构体(无向边大小要开2倍)
int cnt=1;//这是遍历存放边的数组用的,同时也记录着边的数量
int head[1000005];//head[u]表示以u为起点的第一条边(这里第一条指的是链式结构的头)在存放边的数组中的位置 ,无向边大小要开二倍
void add(int u,int v,int w)//加边函数
{
dat[cnt].w=w;//存放边权
dat[cnt].to=v;//存放终点
dat[cnt].next=head[u];//当前边指向上一条同起点的边
head[u]=cnt++;//改变头结点为当前边(方便以后遍历)
}
//------------------------------------------------------------------------------------
int dep[500005];//表示各点的深度
int f[500005][25];//f[a][b]表示从a点走2^b个点到达的位置
void dfs(int v,int father)//预处理函数,维护各点层数和预处理数组,v表示搜索树的根节点,father表示父节点,main中调用的时候填入树根节点和0
{
dep[v]=dep[father]+1;//先把层数维护好
for(int i=1;(1<<i)<=dep[v];++i)
{
f[v][i]=f[f[v][i-1]][i-1];//预处理数组,v+2^i==v+2^(i-1)+2^(i-1)
}
for(int i=head[v];i;i=dat[i].next)//遍历
{
int p1=dat[i].to;//得到当前点的终点
if(p1==father) continue;//双向边,防止回去
f[p1][0]=v;//p1向上跳2^0步到达的是v点
dfs(p1,v);//以p1为搜索树的根节点继续dfs
}
}
int lca(int x,int y)//返回值是x和y的最近公共祖先
{
if(dep[x]<dep[y]) swap(x,y);//总是让x的层数>y的层数方便操作
for(int i=20;i>=0;--i)//从2^20开始尝试
{
if(dep[f[x][i]]>=dep[y]) x=f[x][i];//x尝试在不超过y层数的情况下往上跳,直到跳到同一层
if(x==y) return x;//若跳到了同一层并且xy重合了就返回x,此时这个值就是x和y的最近公共祖先
}
for(int i=20;i>=0;--i)//x跳到和y一层后,让它俩一起往上跳,直到跳到还有一步重合
{
if(f[x][i]!=f[y][i])//如果跳了之后不相等就跳
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];//最后x的父亲(当然也是y的父亲)就是x和y的最近公共祖先
}
int main()
{
int n,m,s,a,b;
cin>>n>>m>>s;
for(int i=1;i<n;++i)
{
cin>>a>>b;
add(a,b,1);
add(b,a,1);//无向边,dfs中特判了所以从儿子回不到父亲
}
dfs(s,0);//参数传入树根节点和0
for(int i=1;i<=m;++i)
{
cin>>a>>b;
cout<<lca(a,b)<<endl;
}
return 0;
}
/*
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
4
4
1
4
4
*/