【转】最近公共祖先(LCA)
基本概念
LCA:树上的最近公共祖先,对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。
RMQ:区间最小值查询问题。对于长度为n的数列A,回答若干询问RMQ(A,i,j),返回数列A中下标在[i,j]里的最小值下标。
朴素LCA算法
求出树上每个结点的深度。
对于查询LCA(u,v),用p1、p2指向将u、v,将p1、p2中深度较大的结点不断指向其父结点,直到p1、p2深度相同。
之后p1、p2同步向上移动,直到p1=p2,此时p1、p2所指向的结点就是LCA(u,v)。
LCA向RMQ转化
对有根树进行DFS,将遍历到的结点按顺序记录下来,将会得到一个长度为2N-1的序列,称之为T的欧拉序列F。
每个结点都在欧拉序列中出现,记录结点u在欧拉序列中第一次出现的位置为pos[u]。
记录结点u的深度为dep[u],在深度序列中记录欧拉序列中的结点的深度B[1...2N-1]。
根据DFS的性质,对于两结点u、v,从pos[u]遍历到pos[v]的过程中会经过LCA[u,v],它的深度是深度序列B[pos[u]...pos[v]]中最小的。
那么求LCA(T,u,v),就等价于求RMQ(B,u,v)。

LCA的Tarjan算法
解决LCA问题的Tarjan算法利用并查集在一次DFS(深度优先遍历)中完成所有询问。它是时间复杂度为O(N+Q)的离线算法,这里的Q表示查询次数。
算法DFS有根树T,定义从根节点到当前正在遍历的结点u的路径为活跃路径P。
对于每个已经遍历过的结点x,我们使用并查集将其连接到P上距离其最近的结点F(x)。
记录与u有关的询问集合为Q(u)。
对于Q(u)中的任意一组询问LCA(u, v),如果v已经遍历过,那么答案即为F(v)。
我们只需要维护当前所有以遍历结点的F即可。
代码流程Tarjan_DFS(u):
- 创建并查集u
- 遍历Q(u)中的所有询问(u,v),如果v已经被标记,则Answer(u,v)=v所在集合的根。
- 对于u的每一个儿子v,调用Tarjan_DFS(v)。合并u与v所在的集合,设根为u。
- 标记u。
倍增LCA
与RMQ的ST算法类似,我们令F[i][0]为结点i的第2k个父结点。
则F[i][0]为i的父结点,令w为i的第2k-1个父结点即w=F[i][k-1],那么w的第2k-1个父结点就是i的第2k个父结点即F[i][k]=F[w][k-1]。
在查询LCA时,与朴素LCA类似,先将深度较大的结点u提升到与v的深度相同,而这一次我们利用倍增法,一次提升2k个父结点,加快了算法的效率。
之后,两个结点同时提高2k(k是使2k<=dep[u]最大的正整数)。直到u、v到达同一个结点。那么这个结点就是LCA(u,v)。
倍增法的优点在于,除了能求出LCA(u,v),还可以对树上的路径进行维护。
例如要求出结点u到结点v路径上最大的边权w,我们可以在预处理F[i][k]时,用一个数组maxCost[i][k]记录结点i到它的第2k个父结点的路径上最大的边权。
那么在查询LCA(u,v)的过程中,求出u、v到公共祖先的路径上的最大边权,即u到v的路径上的最大边权。
void preprocess(){
for (int i=;i<=n;i++){
anc[i][]=fa[i];
maxCost[i][]=cost[i];
for (int j=;(<<j)<n;j++) anc[i][j]=-;
}
for (int j=;(<<j)<n;j++){
for (int i=;i<=n;i++){
if (anc[i][j-]!=-){
int a=anc[i][j-];
anc[i][j]=anc[a][j-];
maxCost[i][j]=max(maxCost[i][j-],maxCost[a][j-]);
}
}
}
}
int query(int p,int q){
int log;
if (L[p]<L[q]) swap(p,q);
for (log=;(<<log)<=L[p];log++);log--;
int ans=-INF;
for (int i=log;i>=;i--){
if (L[p]-(<<i)>=L[q]){
ans=max(ans,maxCost[p][i]);
p=anc[p][i];
}
}
if (p==q) return ans;
for (int i=log;i>=;i--){
if (anc[p][i]!=-&&anc[p][i]!=anc[q][i]){
ans=max(ans,maxCost[p][i]);
p=anc[p][i];
ans=max(ans,maxCost[q][i]);
q=anc[q][i];
}
}
ans=max(ans,cost[p]);
ans=max(ans,cost[q]);
return ans;
}
【转】最近公共祖先(LCA)的更多相关文章
- Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)
Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...
- POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)
POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...
- POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)
POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...
- [模板] 最近公共祖先/lca
简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...
- 【lhyaaa】最近公共祖先LCA——倍增!!!
高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...
- POJ 1470 Closest Common Ancestors (最近公共祖先LCA 的离线算法Tarjan)
Tarjan算法的详细介绍,请戳: http://www.cnblogs.com/chenxiwenruo/p/3529533.html #include <iostream> #incl ...
- 【Leetcode】查找二叉树中任意结点的最近公共祖先(LCA问题)
寻找最近公共祖先,示例如下: 1 / \ 2 3 / \ / \ 4 5 6 7 / \ ...
- 最近公共祖先LCA(Tarjan算法)的思考和算法实现
LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了f ...
- 查找最近公共祖先(LCA)
一.问题 求有根树的任意两个节点的最近公共祖先(一般来说都是指二叉树).最近公共祖先简称LCA(Lowest Common Ancestor).例如,如下图一棵普通的二叉树. 结点3和结点4的最近公共 ...
- 最近公共祖先(LCA)的三种求解方法
转载来自:https://blog.andrewei.info/2015/10/08/e6-9c-80-e8-bf-91-e5-85-ac-e5-85-b1-e7-a5-96-e5-85-88lca- ...
随机推荐
- PHP正则表达式详解(一)
前言: 半年前我对正则表达式产生了兴趣,在网上查找过不少资料,看过不少的教程,最后在使用一个正则表达式工具RegexBuddy时,发现他的教程写的非常好,可以说是我目前见过最好的正则表达式教程.于是一 ...
- VS2013编译Qt5.6.0静态库
获取qt5.6.0源码包 直接去www.qt.io下载就好了,这里就不详细说了. 这里是我已经编译好的** 链接:http://pan.baidu.com/s/1pLb6wVT 密码: ak7y ** ...
- 用 nssm 把 Nginx 安装成 Windows 服务方法
总之:用 nssm 比 srvany.exe 简便多了.1. 下载nginx windows版本:http://nginx.org/ 2. 下载 nssm :http://nssm.cc/3. 安装N ...
- Qt:正则表达式语法:
正则表达式是验证输入.从输入中提取数据以及对输入进行搜索和替换的强大工具,所谓正则表达式,regexp是一种利用模式匹配语言来描述字符串组成限制条件的方式; Qt 提供了一个Q ...
- PHP 链接数据库1(连接数据库&简单的登录注册)
对 解析变量的理解 数据库的名称和表的名称不能重复 从结果中取出的数据 都是以数组的形式取出的 1.PHP查询数据库中的某条信息 //PHP链接数据库 /*1.造链接对象 IP地址 用户名 密码 ...
- 【python】引用其他目录文件
假设有 目录/A(a.py), 目录/B(b.py), 括号里是目录中的文件 在目录/A中编写a2.py,里面可以import a,但是不能import b 解决方法 import sys sys.p ...
- SQL入门语句之LIKE、GLOB和LIMIT
一.SQL入门语句之LIKE LIKE用来匹配通配符指定模式的文本值.如果搜索表达式与模式表达式匹配,LIKE 运算符将返回真(true),也就是 1.这里有两个通配符与 LIKE 运算符一起使用,百 ...
- 原生js完成拼图小游戏
废话不说,看代码,图片可以自己找,我这直接引用了百度的了 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml ...
- libqrencode生成二维码
在生成二维码的库中QREncoder最为常见,但是由于中文字符的特殊性,生成中文的时候会出现一定的错误,所以博主改用libqrencode,是一个纯C编写的类库,支持面也更广泛. ① 下载libqre ...
- Guava学习笔记(3):复写的Object常用方法
转自:http://www.cnblogs.com/peida/p/Guava_Objects.html 在Java中Object类是所有类的父类,其中有几个需要override的方法比如equals ...