[知识点]最近公共祖先
2015-11-01 18:36
316 查看
1、前言
最近公共祖先(LCA),作为树上问题,应用非常广泛,而求解的方式也非常多,复杂度各有不同,这里对几种常用的方法汇一下总。
2、基本概念和暴力算法
最近公共祖先,顾名思义,指的是两个点的公有祖先中,最近的那个点。它显然不会作为一个单独的知识点拿出来考,但是在很多题目中,出现的频率很高,不同场合用不同的方法。先考虑最简单的做法。找祖先,显然是从所查询的两个点从下往上爬,直到两个点出现第一次重叠时,就是最近公共祖先了。首先我们预处理出所有点的深度及父亲节点。由于两个点深度可能不同,首先我们要保证深度大的点先往上爬到与另一个点深度相同的地方,然后两个点同时向上爬,直到出现重叠。代码如下:
3、倍增LCA
倍增这个概念听名字很好理解,知道倍增思想的,直接运用到求LCA并不是不好理解。首先我们需要预处理出所有节点的各种祖宗关系,,p[i][j]记录节点i的第2^j个祖先,j = 0时,就是他的父亲节点。同上述暴力算法,我们从询问的两个点开始,往他们的这些倍数祖先向上爬,若第2^i个祖先不同,则从第2^(i - 1)个祖先开始,从头开始倍增。时间复杂度为 log 级别。代码如下:
P.S. 之前我一直很傻逼的不清楚,如果最近公共祖先出现在了第2^(i - 1)个祖先到底2^i个祖先之间怎么办啊。
4、树链剖分LCA
树链剖分详细概念见(/article/6239479.html)。为什么树链剖分可以跑LCA?原文提得很清楚,在把重链划分出来之后,可以确定的是重链要么单独一条存在,要么必定相连,故可以用线段树来维护。
树链剖分的优势和倍增是一样的,都可以跳过一些不必要的部分,若当前点的重链顶端不是父亲节点,可以利用线段树直接从当前点跳到顶端。这样可以达到减小复杂度的目的。
这里写出树链剖分DFS预处理和LCA核心代码的最新版本。代码如下:
(注释:dep为节点深度,son为儿子节点个数,hv为重儿子节点,top为重链顶端,fa为父亲节点)
5、Tarjan LCA
(暂略)
最近公共祖先(LCA),作为树上问题,应用非常广泛,而求解的方式也非常多,复杂度各有不同,这里对几种常用的方法汇一下总。
2、基本概念和暴力算法
最近公共祖先,顾名思义,指的是两个点的公有祖先中,最近的那个点。它显然不会作为一个单独的知识点拿出来考,但是在很多题目中,出现的频率很高,不同场合用不同的方法。先考虑最简单的做法。找祖先,显然是从所查询的两个点从下往上爬,直到两个点出现第一次重叠时,就是最近公共祖先了。首先我们预处理出所有点的深度及父亲节点。由于两个点深度可能不同,首先我们要保证深度大的点先往上爬到与另一个点深度相同的地方,然后两个点同时向上爬,直到出现重叠。代码如下:
int LCA(int x, int y) { if (dep[x] < dep[y]) swap(x, y); for (; dep[x] != dep[y]; x = fa[x]); for (; x != y; x = fa[x], y = fa[y]); return x; }
3、倍增LCA
倍增这个概念听名字很好理解,知道倍增思想的,直接运用到求LCA并不是不好理解。首先我们需要预处理出所有节点的各种祖宗关系,,p[i][j]记录节点i的第2^j个祖先,j = 0时,就是他的父亲节点。同上述暴力算法,我们从询问的两个点开始,往他们的这些倍数祖先向上爬,若第2^i个祖先不同,则从第2^(i - 1)个祖先开始,从头开始倍增。时间复杂度为 log 级别。代码如下:
void init() { for (int j = 1; (1 << j) <= n; j++) for (int i = 1; i <= n; i++) if (p[i][j - 1] != -1) p[i][j] = p[p[i][j - 1]][j - 1]; } int query(int x, int y) { int o; if (dep[x] < dep[y]) swap(x, y); for (o = 0; (1 << o) <= dep[x]; o++); o--; for (int i = o; i >= 0; i--) if (dep[x] - (1 << i) >= dep[y]) x = p[x][i]; if (x == y) return x; for (int i = o; i >= 0; i--) if (p[x][i] != -1 && p[x][i] != p[y][i]) x = p[x][i], y = p[y][i]; return p[x][0]; }
P.S. 之前我一直很傻逼的不清楚,如果最近公共祖先出现在了第2^(i - 1)个祖先到底2^i个祖先之间怎么办啊。
4、树链剖分LCA
树链剖分详细概念见(/article/6239479.html)。为什么树链剖分可以跑LCA?原文提得很清楚,在把重链划分出来之后,可以确定的是重链要么单独一条存在,要么必定相连,故可以用线段树来维护。
树链剖分的优势和倍增是一样的,都可以跳过一些不必要的部分,若当前点的重链顶端不是父亲节点,可以利用线段树直接从当前点跳到顶端。这样可以达到减小复杂度的目的。
这里写出树链剖分DFS预处理和LCA核心代码的最新版本。代码如下:
void DFS(int o, int d) { dep[o] = d, son[o] = 1; for (int x = h[o]; x; x = edge[x].next) { int v = edge[x].v; if (v == fa[o]) continue; DFS(v, d + 1), son[o] += son[v], hv[o] = !hv[o] || son[hv[o]] < son[v] ? v : hv[o]; } } void DFS2(int o, int t) { top[o] = t; if (!hv[o]) return; DFS2(hv[o], t); for (int x = h[o]; x; x = edge[x].next) { int v = edge[x].v; if (v != hv[o] && v != fa[o]) DFS2(v, v); } } int query(int x, int y) { int f1 = top[x], f2 = top[y]; while (f1 != f2) { if (dep[f1] < dep[f2]) swap(f1, f2), swap(x, y); x = fa[f1], f1 = top[x]; } return dep[x] < dep[y] ? x : y; }
(注释:dep为节点深度,son为儿子节点个数,hv为重儿子节点,top为重链顶端,fa为父亲节点)
5、Tarjan LCA
(暂略)
相关文章推荐
- SD实践代码
- 使用AFNetworking遇到的2个警告解决方法。
- github的入门使用
- C#中静态与非静态方法比较
- 大道至简(第五i章)读后感
- TCMalloc:线程缓冲的Malloc
- .Net项目实战:DropDownList 用法
- 保护HTTP的安全
- 信息安全系统设计基础期中总结-吕松鸿
- iOS UI 懒加载
- C-全局变量与局部变量
- Mark,Mark,准备做智能小车机器人啦~~~
- Hbase-1.1.2完全分布式安装教程
- 广告广告歌
- HDU 5538 House Building(矩阵处理 表面积 水)——2015ACM/ICPC亚洲区长春站
- bestcoder#61hdu5523Game
- Linux1:Linux概述
- 课后作业2015.11.1
- 深入理解asp.net SessionState
- java编程之美(一)