最近公共祖先(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), 首先先序遍 ...
随机推荐
- 【java基础】Java锁机制
在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容如下: 公平锁/非公平锁 可重入锁 独享锁/共享锁(广义) 互斥锁/读写锁(独享锁/共享锁的实现) 乐观锁 ...
- 众皓网络(T 面试)
1.你们项目中哪里用到了Redis? 2.Redis中存储的数据,你们什么时候进行更新? 3.你用过消息队列吗? 4.你写的这个微服务项目拆分成了几个服务? 5.SpringCloud项目怎么部署的?
- easyui权限管理
在easyui上实现权限的管理 所谓权限:指的是系统中的资源,资源包括菜单资源(学习情况报表,账号审核...)以及按钮资源所谓角色:指的是系统中的权限集合(每一个角色对应着哪些权限集合) 1.一星权限 ...
- 【原创翻译】链接DLL至可执行文件---翻译自MSDN
可执行文件.exe链接(或加载)DLL有以下两种形式: 隐式链接 显式链接 隐式链接是指静态加载或在程序加载时动态链接. 通过隐式链接,在使用DLL时,可执行文件链接到一个由生成DLL的人提供的导入函 ...
- 1.1 Qt入门
学习Qt的前提是学好C++. 我刚入门Qt,打算趁着暑假2个月时间来学习<C++ GUI Qt 4>这本书. 现在有Qt4和Qt5,似乎很多公司都还是在使用Qt4,所以我也就选择了学习Qt ...
- EBS ORACLE使用API批量取消销售订单
需要切换组织,还有用户的id.下面红色字体代表要修改的地方. /*BEGIN MO_GLOBAL.INIT('M'); MO_GLOBAL.set_policy_context ('S',); FND ...
- sed替换字符串中的某些字符
test.txt原文内容 http://jsldfjaslfjsldfjasl/test?jlsdfjsalfjslfd 使用sed替换 sed -ri 's/(http:\/\/)[^\/]*(\/ ...
- springboot @test 使用
@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) public class Springtest { ...
- 微信小程序之裁剪图片成圆形
前言 最近在开发小程序,产品经理提了一个需求,要求微信小程序换头像,用户剪裁图片必须是圆形,也在github上看了一些例子,一般剪裁图片用的都是方形,所以自己打算写一个小组件,可以把图片剪裁成圆形,主 ...
- Yii1 获取当前请求的url
echo Yii::app()->getRequest()->getUrl();