最近公共祖先(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), 首先先序遍 ...
随机推荐
- guanbi selinux
编辑/etc/sysconfig/selinux,把第一条选项改为 disabled
- python基础一day3 字符串
对字符串进行的任何操作都是形成新的字符串. 切片顾头不顾尾 倒着取: 因为顾头不顾尾,所以4要取到 当步长省略时,可以同时省略最后一个冒号 写0时,取不到,什么都不写,可以取到 倒着取出全部的值,两种 ...
- 通过 getResources 找不到jar包中的资源和目录的解决方法
http://my.oschina.net/sub/blog/184074 今天碰到一个怪问题: 原本跑的好好的代码,打成 jar 包就不能运行了. 问题出在,代码中有一段自动扫描 classpath ...
- PHP21 MVC
学习目标 MVC设计模式 单一入口机制 MVC的实现 MVC设计模式 Model(模型) 是应用程序中用于处理应用程序数据逻辑的部分.通常模型对象负责在数据库中存取数据. View(视图) 是应用程序 ...
- react笔记汇总
1.什么是React? a.React 是一个用于构建用户界面的 JAVASCRIPT 库. b.React主要用于构建UI,很多人认为 React 是 MVC 中的 V. c.React 起源于 F ...
- 前端学习日记-vue cli3.0环境搭建
卸载老版本的 vue-cli : npm uninstall vue-cli -g 安装新版本的 : npm install -g @vue/cli --安装新版本cli 同时nodeJS 要更新至 ...
- IDEA ctrl+alt+L 格式化快捷键无效时解决
这几天发现自己Intellij IDEA ctrl+alt+L格式化代码无效 设置里面按照快捷键搜索 按了 ctrl+alt+L 也没反应 但是我设置的确实是默认的 ctrl+alt+L 最后终于找到 ...
- zabbix 3.4安装
一.server安装 [root@zabbix ~]# cat /etc/redhat-release CentOS Linux release (Core) [root@zabbix ~]# una ...
- 条款10:令operator=返回一个reference to * this(Have assignment operators return a reference to *this)
NOTE: 1.令赋值(assignment)操作符返回一个reference to *this. 2.此协议适用于所有赋值相关的运算比如:+= -= *=....
- BeautifulSoup与aiohttp的简单应用-爬取《网上中华五千年》音频
香港电台的节目素质都比较不错,其中有个<中华五千年>的节目是以情景剧与旁白的形式来展示历史故事,由传说时代一直到民国,1983年首播至2000年,非常长寿的一个节目.网上能找到版本声音非常 ...