LCA问题,即最近公共祖先问题,有很多种解法,其中比较高效的有在线的转化为ST表的算法、树上倍增算法,和离线的tarjan算法。在线的算法都很简单,这里要讲的是离线的tarjan算法。
思想
tarjan算法其实也并不难理解,它的主要思想就是利用了DFS的深度优先的顺序,算法的主框架就是一个DFS遍历,同时利用了并查集的快速合并。理解的时候可以对DFS的过程进行拆分,把访问一个节点过程拆分为访问它和退出访问,退出访问即回溯,由于DFS的顺序是每一条路径都走到底,然后往回退一步,再访问上一层的其他儿子,再退,再访问上一层的上一层的其他儿子……,并查集中的fa数组记录的就是每一个节点当前往回回溯到哪一个点。对于每个节点,访问和它有关的每一个询问,如果询问中的另一个节点已经访问过了,那么这一次询问的答案就是另一个节点的fa(当前它往回回溯到的节点)。每搜索了一个节点就要将它的fa与父节点进行合并(注意合并的顺序)。
代码
下面给出poj1330的代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 10006
using namespace std;
int tet,n,tot,ans,Root,X,Y,fa[maxn],lnk[maxn],son[maxn],nxt[maxn];
bool vis[maxn],isR[maxn];
inline char nc(){
static char buf[100000],*i=buf,*j=buf;
return i==j&&(j=(i=buf)+fread(buf,1,100000,stdin),i==j)?EOF:*i++;
}
inline int _read(){
char ch=nc();int sum=0;
while(!(ch>='0'&&ch<='9'))ch=nc();
while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
return sum;
}
void add(int x,int y){
nxt[++tot]=lnk[x];son[tot]=y;lnk[x]=tot;
}
int get(int x){return fa[x]==x?x:fa[x]=get(fa[x]);}
void merge(int x,int y){
x=get(x);y=get(y);
if(x!=y)fa[x]=y;
}
void tarjan(int x){
vis[x]=0;
for(int j=lnk[x];j;j=nxt[j]) if(vis[son[j]]) tarjan(son[j]),merge(son[j],x);
if(x==X&&!vis[Y])ans=get(Y);
if(x==Y&&!vis[X])ans=get(X);
}
int main(){
freopen("lca.in","r",stdin);
freopen("lca.out","w",stdout);
tet=_read();
while(tet--){
tot=0;memset(lnk,0,sizeof(lnk));memset(vis,1,sizeof(vis));memset(isR,1,sizeof(isR));
n=_read();
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1,x,y;i<n;i++)x=_read(),y=_read(),add(x,y),isR[y]=0;
X=_read();Y=_read();
for(int i=1;i<=n;i++) if(isR[i])Root=i;
tarjan(Root);
printf("%d\n",ans);
}
return 0;
}