树上两点的最近公共祖先

  对于有根树T的两个节点u和v,最近公共祖先LCA(T,u,v)表示一个接点x,满足x是u、v的祖先且x的深度尽可能大。对于点x来说,有一点非常特殊,那就是从u到v的路劲一定经过点x。

  下面讨论用Tarjan算法求解该问题。

  Tarjan算法基于深度优先搜索的框架,对于新搜索到的一个节点,首先创建由这个节点构成的集合,再对当前点的每个子树进行搜索,每搜索完一棵子树,则可以确定子树内的LCA询问都已解决。其他的LCA询问的结果必然在这个子树之外,这时把子树所形成的集合与当前节点的集合合并,并将当前节点设为这个集合的祖先。之后继续搜索下一棵子树,直到当前节点的所有子树搜索完。这时把当前节点也设为已被检查过的,同时可以处理有关当前节点的LCA询问,如果有一个从当前节点到节点v的询问,且v已被检查过,则由于进行的是深度优先搜索,当前节点与v的最近公共祖先一定还没有被检查,而这个最近公共祖先的包含v的子树一定已经搜索过了,那么这个最近公共祖先一定是v所在集合的祖先。

  伪代码:

  对于每一点u:

  ①建立以u为代表元素的集合。

  ②遍历与u相连的节点v,如果没有被访问过,对于v使用Tarjan算法,结束后,将v的集合并入u的集合。

  ③对于与u有关的询问(u,v),如果v被访问过,则结果就是v所在集合的代表元素。

  算法实现:

  使用链式前向星存储图和所有询问,head[]和edge[],qhead[]和qedge[]表示询问。由于链式前向星只能存储有向边,也就是说用其存储图和询问的时候要存储(u,v)和(v,u)两次。即对于每条询问在qedge中有两条,但是只会对其中一条有应答,当然应答时可以对另一条直接赋值。对于集合的操作用并查集来完成。

  代码如下:

 1 int p[maxn];
 2 int head[maxn];
 3 int qhead[maxn];
 4 struct NODE
 5 {
 6     int to,next,lca;
 7 };
 8 NODE edge[maxm];
 9 NODE qedge[maxq];
10 int find(int x)
11 {
12     if(p[x]!=x)p[x]=find(p[x]);
13     return p[x];
14 }
15 bool vis[maxn];
16 void LCA(int u)
17 {
18     p[u]=u;
19     int k;
20     vis[u]=true;
21     for(k=head[u];k!=-1;k=edge[k].next)
22     {
23         if(!vis[edge[k].to])
24         {
25             LCA(edge[k].to);
26             p[edge[k].to]=u;
27         }
28     }
29     for(k=qhead[u];k!=-1;k=qedge[k].next)
30     {
31         if(vis[qedge[k].to])
32         {
33             qedge[k].lca=find(qedge[k].to);
34             qedge[k^1].lca=qedge[k].lca;
35         }
36     }
37 }
View Code

相关文章: