LCA(Tarjan)
概念
对于有根树T的两个结点\(u\)、\(v\),最近公共祖先\(LCA(T,u,v)\)表示一个结点\(x\),满足\(x\)是\(u\)和\(v\)的祖先且\(x\)的深度尽可能大。在这里,一个节点也可以是它自己的祖先。 ——摘自 百度百科
思路
\(Tarjan\)算法的思路如下。
- 从根开始\(DFS\),对于遍历到的每个节点,若不为叶子,则遍历其儿子,否则进行操作\(2\)。
- 为叶子节点,则先标记该节点为已访问,若有关于它的操作,进行操作\(3\),否则进行操作\(4\)。
- 遍历所有操作,若操作另一节点已被访问,则这两点的最近公共祖先为另一节点的\(f\)值。所有操作遍历完后,进行操作\(4\)。
- 回溯,并将该节点与其父亲合并。
典例
qzez1916 祖孙询问
大意
给定一棵有\(n\)个节点的树,输入\(n\)条边后,给定\(m\)次询问,每次给出\(a_i,b_i\),求这两点的最近公共祖先。
代码
#include<iostream>
#include<cstdio>
#define maxn 500005
using namespace std;
int n,m,s;
int x,y;
int fa[maxn];
int sum1=0,sum2=0;
struct node{
int head,to,nxt;
}a[maxn*2];
void add(int x,int y){
a[++sum1].to=y;
a[sum1].nxt=a[x].head;
a[x].head=sum1;
}
struct node2{
int head,to,nxt;
}f[maxn*2];
void add2(int x,int y){
f[++sum2].to=y;
f[sum2].nxt=f[x].head;
f[x].head=sum2;
}
int find(int x){
if(fa[x]==x){
return x;
}else{
return find(fa[x]);
}
}
bool vis[maxn];
int ans[2*maxn];
void tarjan(int s){
vis[s]=1;
for(int i=a[s].head;i;i=a[i].nxt){
int v=a[i].to;
if(vis[v]){
continue;
}
tarjan(v);
fa[v]=s;
}
for(int i=f[s].head;i;i=f[i].nxt){
int v=f[i].to;
if(vis[v]){
ans[i]=find(v);
if(i%2==0){
ans[i-1]=ans[i];
}else{
ans[i+1]=ans[i];
}
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=n;i++){
fa[i]=i;
}
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
add2(x,y);
add2(y,x);
}
tarjan(s);
for(int i=1;i<=2*m;i+=2){
printf("%d\n",ans[i]);
}
return 0;
}