理解LCA离线算法

该算法也是tarjan发现的,故也叫tarjan算法。
这个算法的主体还是dfs,先看算法框架:

void make_set(int i)
{
 p[i]=i;
}

int find_set(int i)
{
 if(i!=p[i]) p[i]=find_set(p[i]);
 return p[i];
}

union_set(int i,int j)
{
 i=find_set(i),j=find_set(j);
 p[j]=i;
}
//tarjan算法主体
void dfs(int u)
{
 int i,v;
 make_set(u);
 for(i=0;i<g[u].size();i++)
 {
  v=g[u][i];
  if(p[v]==-1)
  {
   dfs(v);
   union_set(u,v);
  }
 }
 for(v=0;v<n;v++)
 {
  if(p[v]!=-1)
  {
   lca[u][v]=lca[v][u]=find_set(v);
  }
 }
}

前3个是并查集的函数,这里就不分析了,主要分析dfs:

当dfs到某个结点时,该结点自成一个集合,即p[u]=u,这个数组同时还能作为dfs的标记,当完成某棵子树的搜索后,假设该子树的根为u,任意其他已经标记过的结点v,

若v是u的祖先,则可以肯定以v为根的子树的搜索尚未完成,所以v仍然自成一个集合,此时lca(u,v)=p[v]=v;

若v是u的子孙结点,则可以肯定以v为根的子树的搜索已经完成,v已经被并入u所在的集合,所以lca(u,v)=p[v]=u;

若v是u的兄弟结点或其兄弟结点的子孙结点,设u的父亲结点为w,则可以肯定以v为根的子树的搜索已经完成,但以w为结点的子树的搜索尚未完成,所以v已经并入w所在集合,lca(u,v)=p[v]=p[w]=w;

终上所述,每次完成以u为根的子树的dfs时,对于其他已经标记过的结点v,lca(u,v)=p[v]。

完成对子树的搜索和询问后,需将子树根结点并入父结点所在集合,这也是整个算法的精妙所在。

原文地址:https://www.cnblogs.com/algorithms/p/2580290.html