最近公共祖先(Least Common Ancestors)
题意:
给定一棵有根树T,给出若干个查询lca(u, v)(通常查询数量较大),每次求树T中两个顶点u和v的最近公共祖先,即找一个节点,同时是u和v的祖先,并且深度尽可能大(尽可能远离树根)。通常有以下几种算法:
- 在线算法,每次读入一个查询,处理这个查询,给出答案。
- 离线算法,一次性读入所有查询,统一进行处理,给出所有答案。
在线:
倍增(基于二分搜索):
基本思想就是让u和v同时走到同一高度,然后再一起一步步往上走。
将父亲结点的父亲结点利用起来,依次计算,便可以得到从当前结点向上走2k步所到达的顶点,这样便有了k以内的点的所有信息,进行二分查找答案即可~
预处理时间复杂度O(nlogn),查询时间复杂度O(logn)。
关键代码:
首先预处理阶段
//DFS预处理所有结点的深度和父节点
void dfs(int v, int p, int d)
{
pa[0][v] = p;
dept[v] = d;
for(int i = head[v]; i != -1; i = edge[i].next){
int u = edge[i].to;
if(u == p) continue;
dfs(u, v, d + 1);
}
}
void init()
{
dfs(root, -1, 0);
//预处理祖先,向上走2^i所到的结点
for(int i = 0; i < maxm - 1; i++){
for(int j = 1; j <= V; j++){
if(pa[i][j] < 0) pa[i + 1][j] = -1;
else pa[i + 1][j] = pa[i][pa[i][j]];
}
}
}
计算u和v的lca
int lca(int u, int v)
{
//让u和v 向上走到同一高度
if(dept[u] > dept[v]) swap(u, v);
for(int i = 0; i < maxm; i++){
if((dept[v] - dept[u]) >>i &1)
v = pa[i][v];
}
if(u == v) return u;
//二分搜索计算lca
for(int i = maxm - 1; i >= 0; i--){
if(pa[i][u] != pa[i][v]){
u = pa[i][u];
v = pa[i][v];
}
}
return pa[0][u];
}
基于RMQ的算法:
初始化过程O(nlogn),查询过程O(1)。
有根树处理的一个技巧就是将树转化为从根DFS标号后得到的序列。而这种算法的基本思想就是将树看成一个无向图,u和v的公共祖先一定在u和v之间的最短路上。
算法分三步:
- 首先DFS对结点从跟开始标号,用数组vs保存访问顺序,height记录深度。每条边恰好经过两次,因此一共记录了2n−1个结点
- 计算对于每个顶点首次出现子的下标,保存在id中。
- 获取LCA(u,v):LCA(u,v)=vs[id[u]≤i≤id[v]中深度最小的i]
预处理:
void dfs(int u, int pre, int dept)
{
vs[cnt] = u;
height[cnt] = dept;
id[u] = cnt++;
for(int i = head[u]; i != -1; i = edge[i].next){
dfs(edge[i].to, u, dept + 1);
vs[cnt] = u;
height[cnt++] = dept;
}
}
void init()
{
cnt = 1; //vs数组下标从1开始
dfs(root, root, 0);
st.init(2 * V - 1);
}
而最后一步属于RMQ(Range Minimum/Maximum Query),即区间最值查询问题,我们可以用线段树解决,也可以使用ST(Sparse Table)算法,在O(nlogn)时间内进行预处理,然后在O(1)时间内回答每个查询。
预处理使用动态规划,设dp[i][j]是从i开始的2j个数中的深度最小的值的下标。则有状态转移方程:
if(height[dp[i][j - 1]] < height[dp[i + (1<<(j - 1))][j - 1]])
dp[i][j] = dp[i][j - 1];
else
dp[i][j] = dp[i + (1<<(j - 1))][j - 1];
初始化:
for(int i = 1; i <= n; i++) dp[i][0] = i;
查询:
int query(int a, int b)
{
if(a > b) swap(a, b);
int k = lg[b - a + 1] ;
if(height[dp[a][k]] <= height[dp[b - (1<<k) + 1][k]])
return dp[a][k];
else
return dp[b - (1<<k) + 1][k];
}
离线Tarjan算法:
Tarjan算法是离线算法,基于后序DFS和并查集。
算法从根节点root开始搜索,每次递归搜索所有的子树,然后处理跟当前根节点相关的所有查询。算法用集合表示一类节点,这些节点跟集合外的点的LCA都一样,并把这个LCA设为这个集合的祖先。当搜索到节点x时,创建一个由x本身组成的集合,这个集合的祖先为x自己。然后递归搜索x的所有儿子节点。
所有子树处理完毕之后,处理当前根节点x相关的查询。遍历x的所有查询,如果查询的另一个节点v已经访问过了,那么x和v的LCA即为v所在集合的祖先。
建树可以用数组写链表也可以用vector保存,而查询可以用矩阵保存,这样可以减少重复,也可以用链表的形式,将一个结点的查询连在一起。
Tarjan关键代码:
void LCA(int u)
{
ance[u] = u;
vis[u] = 1;
for(int i = head[u]; i != -1; i = edge[i].next){
int v = edge[i].to;
if(vis[v]) continue;
LCA(v);//访问子树
unite(u, v);//子树与当前结点合并
ance[_find(u)] = u;//祖先为u
}
for(int i = h[u]; i != -1; i = query[i].next){
int v = query[i].q;
if(vis[v]) ans[query[i].index] = ance[_find(v)];
}
}
//感觉这个ance数组完全可以不用~~
最近公共祖先(Least Common Ancestors)的更多相关文章
- 最近公共祖先(least common ancestors algorithm)
lca问题是最近公共祖先问题,一般是针对树结构的.现在有两种方法来解决这样的问题 1. On-line algorithm 用比较长的时间做预处理.然后对每次询问进行回答. 思路:对于一棵树中的两个节 ...
- 最近公共祖先 Lowest Common Ancestors
基于深度的LCA算法: 对于两个结点u.v,它们的深度分别为depth(u).depth(v),对于其公共祖先w,深度为depth(w),u需要向上回溯depth(u)-depth(w)步,v需要d ...
- 最近公共祖先 Least Common Ancestors(LCA)算法 --- 与RMQ问题的转换
[简介] LCA(T,u,v):在有根树T中,询问一个距离根最远的结点x,使得x同时为结点u.v的祖先. RMQ(A,i,j):对于线性序列A中,询问区间[i,j]上的最值.见我的博客---RMQ - ...
- [Swift]LeetCode235. 二叉搜索树的最近公共祖先 | Lowest Common Ancestor of a Binary Search Tree
Given a binary search tree (BST), find the lowest common ancestor (LCA) of two given nodes in the BS ...
- [Swift]LeetCode236. 二叉树的最近公共祖先 | Lowest Common Ancestor of a Binary Tree
Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. According ...
- 最近公共祖先 · Lowest Common Ancestor
[抄题]: Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. “Th ...
- 最近公共祖先 LCA 倍增算法
树上倍增求LCA LCA指的是最近公共祖先(Least Common Ancestors),如下图所示: 4和5的LCA就是2 那怎么求呢?最粗暴的方法就是先dfs一次,处理出每个点的深度 ...
- 学习笔记--最近公共祖先(LCA)的几种求法
前言: 给定一个有根树,若节点\(z\)是两节点\(x,y\)所有公共祖先深度最大的那一个,则称\(z\)是\(x,y\)的最近公共祖先(\(Least Common Ancestors\)),简称\ ...
- [总结]最近公共祖先(倍增求LCA)
目录 一.定义 二.LCA的实现流程 1. 预处理 2. 计算LCA 三.例题 例1:P3379 [模板]最近公共祖先(LCA) 四.树上差分 1. 边差分 2. 点差分 3. 例题 一.定义 给定一 ...
- 编程算法 - 二叉树的最低公共祖先 代码(C)
二叉树的最低公共祖先 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 二叉树的最低公共祖先(lowest common ancestor), 首先先序遍 ...
随机推荐
- CentOS下JRE环境变量配置
很多时候,我们需要在CentOS上部署tomcat,从而搭建web服务器,然JDK/JRE环境是前提,这里就记录一下,在后面的时候直接使用. 下载jre-7u80-linux-x64.tar.gz,并 ...
- 实战角度比较EJB2和EJB3的架构异同
] EJB编程模型的简化 首先,EJB3简化的一个主要表现是:在EJB3中,一个EJB不再象EJB2中需要两个接口一个Bean实现类,虽然我们以前使用JBuilder这样可视化开发工具自动生成了EJB ...
- c# 从DataGridVieew导出到excel
public static bool DataGridViewToExcel(DataGridView dataGridView, bool isShowExcel) { int rowsQty = ...
- JDO
JDO 编辑 本词条缺少名片图,补充相关内容使词条更完整,还能快速升级,赶紧来编辑吧! JDO(Java Data Object )是Java对象持久化的新的规范,也是一个用于存取某种数据仓库中的对象 ...
- C3P0连接池工具类实现步骤及方法
C3P0连接池的工具类 使用C3P0获得连接对象连接池有一个规范接口 javax.sal.DataSourse 接口定义了一个从连接池中获得连接的方法getConnection(); 步骤导入jar包 ...
- b继承a的函数
var cls={ my:, init:function() { alert(this.my.a); }};window.onload=function(){ cls.init();} 调用cls.i ...
- c++ extern
一.extern关键字的作用 文件中定义的全局变量的可见性扩展到整个程序是在链接完成之后,而在编译阶段,他们的可见性仍局限于各自的文件. 编译器的目光不够长远,编译器没有能够意识到,某个变量符号虽然不 ...
- sqlserver还原3101
1.出现错误"3101" 2.解决办法:删除数据库之后还原(有风险)或者获得数据库的独占访问权(用sql语句) 参考:https://www.2cto.com/database/2 ...
- [CF] 37 E. Trial for Chief
如果固定了一个中心,那么只需要考虑从它开始最远染到的那些点究竟染了几次. 上下左右不同的点连1边,相同的连0边,跑单源最短路就可以啦. lyd讲的是统计到最远黑点+1的最小值,但是#58数据全是白点, ...
- 快速部署jumpserver堡垒机
jumpserver版本:Version 1.4.1-2 (社区版) 主机IP地址:10.0.0.105 准备环境1.安装依赖yum -y install wget sqlite-devel xz g ...