标签:
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