算法描述》LCA两三事(蒟蒻向)

  LCA是图论中常用的解决树形结构子问题的工具,这一问题一般需要用一个简短的子函数直接解决,但是这对于广大蒟蒻们仍然是一个不小的问题。

  LCA是指在树形结构中两点的最近公共祖先,对于这个问题,直接向上找事最直接的方法,但同时时间复杂度和数据给出的生成树的层数有关,最优情况是logN级别的,但是如果数据给出的是一条链就GG了,所以要用更优的方法写,一般来说,用的是log2N的操作,最糟糕的复杂度也是logN级别的,那如何实现这一过程捏,我这里有两种方法,和大家分享

  第一种:树上倍增

  具体方法是对于已经预处理好的f[i][j]数组来实现这一过程,预处理具体过程如下

1 for(int j=1;j<=n;++j)
2     f[0][j]=father[j];
3 for(int i=1;i<=20;++i)
4     for(int j=1;j<=n;++j)
5         f[i][j]=f[i-1][f[i-1][j]];

  这段代码需要稍加理解,f[i][j]表示第j个点的2的 i次方的父亲节点。因为一般数据给的不会特别大,所以在 i 的那重循环里,只要到20就够了,因为这是指数级的操作。

  这个预处理是常数级的O(N),只不过常数比较大。

  那么预处理结束后,我们接下来的操作就应该是对于每一组要求LCA的两个数,直接扔进子程序判断

  子程序如下

  

 1 int LCA(int x,int y)
 2 {
 3     if(dep[x]>dep[y])swap(x,y);
 4     for(int i=20;~i;--i)
 5     if(dep[f[i][y]]>dep[x])y=f[i][y];
 6     if(y==x)return x;
 7     for(int i=20;~i;--i)
 8     if(f[i][y]!=f[i][x])y=f[i][y],x=f[i][x];
 9     return f[0][x];
10 }

   这个子程序很简洁明了,只要会二进制拆分就很好理解,在第3行到第6行写的是输入的两点属于祖先与子孙关系,这时我们只要找一边的祖先节点就好啦;如果这两点属于不同子树,那就缩为同一深度。

  第7到第9行,代表两节点属于两颗子树这时只要不停的趋近就好了,因为我们的判断条件,所以在最后必须输出一个缩后点的父亲节点。

  第二种:欧拉序列

  这是一种更优的写法,主要思路是先将一颗树按欧拉序列处理,然后在每次对欧拉序列中这两点之间的点做一遍RMQ,RMQ的关键字是最小深度。

  但是这种写法较为复杂(我懒),所以代码没有给出,但是只要会RMQ的话这应该就是个较为简单的问题了。

  算法效率分析

  这种方法无论从时间复杂度还是空间复杂度来说都比树上倍增优,但事实上由于树上倍增极小的常数,所以这两个算法在时间复杂度上是相似的,但是在空间上来说欧拉序列更优,从另一个方面来说,树上倍增不仅可以求LCA,还可以顺便求一个路径最大权边之类的问题,并且编程复杂度优,所以一般来说树上倍增是比较常用的。

  有一道半模板题

  BZOJ3732

posted @ 2016-10-02 19:15  PencilWang  阅读(198)  评论(0编辑  收藏  举报