树上两点的最近公共祖先
对于有根树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 }