题意:

给定一棵有根树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)的更多相关文章

  1. 最近公共祖先(least common ancestors algorithm)

    lca问题是最近公共祖先问题,一般是针对树结构的.现在有两种方法来解决这样的问题 1. On-line algorithm 用比较长的时间做预处理.然后对每次询问进行回答. 思路:对于一棵树中的两个节 ...

  2. 最近公共祖先 Lowest Common Ancestors

    基于深度的LCA算法:  对于两个结点u.v,它们的深度分别为depth(u).depth(v),对于其公共祖先w,深度为depth(w),u需要向上回溯depth(u)-depth(w)步,v需要d ...

  3. 最近公共祖先 Least Common Ancestors(LCA)算法 --- 与RMQ问题的转换

    [简介] LCA(T,u,v):在有根树T中,询问一个距离根最远的结点x,使得x同时为结点u.v的祖先. RMQ(A,i,j):对于线性序列A中,询问区间[i,j]上的最值.见我的博客---RMQ - ...

  4. [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 ...

  5. [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 ...

  6. 最近公共祖先 · Lowest Common Ancestor

    [抄题]: Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree. “Th ...

  7. 最近公共祖先 LCA 倍增算法

          树上倍增求LCA LCA指的是最近公共祖先(Least Common Ancestors),如下图所示: 4和5的LCA就是2 那怎么求呢?最粗暴的方法就是先dfs一次,处理出每个点的深度 ...

  8. 学习笔记--最近公共祖先(LCA)的几种求法

    前言: 给定一个有根树,若节点\(z\)是两节点\(x,y\)所有公共祖先深度最大的那一个,则称\(z\)是\(x,y\)的最近公共祖先(\(Least Common Ancestors\)),简称\ ...

  9. [总结]最近公共祖先(倍增求LCA)

    目录 一.定义 二.LCA的实现流程 1. 预处理 2. 计算LCA 三.例题 例1:P3379 [模板]最近公共祖先(LCA) 四.树上差分 1. 边差分 2. 点差分 3. 例题 一.定义 给定一 ...

  10. 编程算法 - 二叉树的最低公共祖先 代码(C)

    二叉树的最低公共祖先 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 二叉树的最低公共祖先(lowest common ancestor), 首先先序遍 ...

随机推荐

  1. EJB2的配置

    1. ejb-jar.xml <?xml version="1.0" encoding="UTF-8"?> <ejb-jar xmlns=&q ...

  2. Oracle Recycle Bin

    开启回收站RECYCLEBIN=ON,默认开启 ALTER SYSTEM SET RECYCLEBIN=OFF SCOPE=SPFILE; 一.从回收站还原表 还原删除的表和从属对象. 如果多个回收站 ...

  3. js递归和数组去重(简单便捷的用法)

    1.递归例子<script type="text/javascript"> function test(num) { if(num < 0) { return; ...

  4. 记忆化搜索 || POJ 1088 滑雪

    从任意一点可以往上下左右比它小的数那里走,问最远长度是多少 *解法:每一点dfs搜索一遍 记忆化搜索:http://blog.csdn.net/acmer_sly/article/details/53 ...

  5. koa源码解读

    koa是有express原班人马打造的基于node.js的下一代web开发框架.koa 1.0使用generator实现异步,相比于回调简单和优雅和不少.koa团队并没有止步于koa 1.0, 随着n ...

  6. 安装Windows10+Ubentu18双系统

    1.先安装Windows系统,安装完成后,使用磁盘管理工具划分出一定的freespace空间留给linux安装系统用. 2.使用Universal-USB-Installer制作ubentu启动U盘. ...

  7. Firefox--摄像头麦克风权限

    在自动化测试的过程中,可能会遇到来自浏览器的权限提示(摄像头.麦克风),今天,就讨论一下如何结局这个问题. 先来认识一下来自Firefox的权限提示,访问一个需要摄像头或者麦克风的网站 你可能觉得,一 ...

  8. 16. PLUGINS

    16. PLUGINS PLUGINS表提供有关服务器插件的信息. PLUGINS表有以下列: PLUGIN_NAME :用于在诸如INSTALL PLUGIN和UNINSTALL PLUGIN之类的 ...

  9. /etc/rc.d启动目录详解

    操作系统:CentOS6.6_32位 控制脚本目录/etc/rc.d,该目录下存在各个运行级别的脚本文件,执行ls /etc/rc.d,显示结果为:init.d  rc  rc0.d  rc1.d   ...

  10. 免费开源《OdooERP系统部署架构指南》试读:第一章 Odoo架构概述

    文/开源智造联合创始人老杨 本文来自<OdooERP系统部署架构指南>的试读章节.书籍尚未出版,请勿转载.欢迎您反馈阅读意见. 从web浏览器到PostgreSQL,多层与其他层交互以处理 ...