码迷,mamicode.com
首页 > 其他好文 > 详细

[知识点]最近公共祖先

时间:2015-11-01 19:30:46      阅读:224      评论:0      收藏:0      [点我收藏+]

标签:

1、前言

  最近公共祖先(LCA),作为树上问题,应用非常广泛,而求解的方式也非常多,复杂度各有不同,这里对几种常用的方法汇一下总。

 

2、基本概念和暴力算法

  最近公共祖先,顾名思义,指的是两个点的公有祖先中,最近的那个点。它显然不会作为一个单独的知识点拿出来考,但是在很多题目中,出现的频率很高,不同场合用不同的方法。先考虑最简单的做法。找祖先,显然是从所查询的两个点从下往上爬,直到两个点出现第一次重叠时,就是最近公共祖先了。首先我们预处理出所有点的深度及父亲节点。由于两个点深度可能不同,首先我们要保证深度大的点先往上爬到与另一个点深度相同的地方,然后两个点同时向上爬,直到出现重叠。代码如下:

1 int LCA(int x, int y)
2 {
3   if (dep[x] < dep[y]) swap(x, y);
4   for (; dep[x] != dep[y]; x = fa[x]);
5   for (; x != y; x = fa[x], y = fa[y]);
6   return x;
7 }

 

3、倍增LCA

  倍增这个概念听名字很好理解,即在一步一步走的基础上,根据条件跳过一些无用层,每步一倍一倍增加。

 

4、树链剖分LCA

  树链剖分详细概念见(http://www.cnblogs.com/jinkun113/p/4683299.html)。为什么树链剖分可以跑LCA?原文提得很清楚,在把重链划分出来之后,可以确定的是重链要么单独一条存在,要么必定相连,故可以用线段树来维护。

  树链剖分的优势和倍增是一样的,都可以跳过一些不必要的部分,若当前点的重链顶端不是父亲节点,可以利用线段树直接从当前点跳到顶端。这样可以达到减小复杂度的目的。

  这里写出树链剖分DFS预处理和LCA核心代码的最新版本。代码如下:

 1 void DFS(int o, int d)
 2 {
 3     dep[o] = d, son[o] = 1;
 4     for (int x = h[o]; x; x = edge[x].next)
 5     {
 6         int v = edge[x].v;
 7         if (v == fa[o]) continue;
 8         DFS(v, d + 1), son[o] += son[v], hv[o] = !hv[o] || son[hv[o]] < son[v] ? v : hv[o];
 9     }
10 }
11 
12 void DFS2(int o, int t)
13 {
14     top[o] = t;
15     if (!hv[o]) return;
16     DFS2(hv[o], t);
17     for (int x = h[o]; x; x = edge[x].next)
18     {
19         int v = edge[x].v;
20         if (v != hv[o] && v != fa[o]) DFS2(v, v);
21     }
22 }
23 
24 int query(int x, int y)
25 {
26     int f1 = top[x], f2 = top[y];
27     while (f1 != f2)
28     {
29         if (dep[f1] < dep[f2]) swap(f1, f2), swap(x, y);
30         x = fa[f1], f1 = top[x];
31     }
32     return dep[x] < dep[y] ? x : y;
33 }

(注释:dep为节点深度,son为儿子节点个数,hv为重儿子节点,top为重链顶端,fa为父亲节点)

 

 

5、Tarjan LCA

(暂略)

 

[知识点]最近公共祖先

标签:

原文地址:http://www.cnblogs.com/jinkun113/p/4928374.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!