【最近公共祖先】浅论lca

2024-03-25 23:30
文章标签 公共 lca 最近 浅论 祖先

本文主要是介绍【最近公共祖先】浅论lca,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

lca

lca(Least Common Ancestors)。顾名思义,就是离两个点最近的公共祖先,换句话说,就是在一棵树中,两个点最短距离中的深度最小的点

                                         图1                                                                图2

 如图所示,在图1中,u和v的lca为q;在图2中,u和w的lca为p。

特别的,节点本身也可以做祖先,如u和q的lca为q。

不难看出,lca可以用作查询树中两点之间最短距离,即计算根节点到两节点u和v的距离是S_{u}S_{v},并算出根节点到它们lca的距离S_{lca(u,v)},于是可以推出(L_{u\rightarrow v})_{min}=S_{u}+S_{v}-2S_{lca(u,v)}

代码实现

如果用暴力做法,面对严格的时间条件必然是不行的,它的时间复杂度往往不能满足需求。因此,我们需要一些更为巧妙的方法:

1. 倍增思想

倍增法通俗的来讲就是每一步尽可能跳的多一点。假设我们要求解lca(u,v),最为直截了当的做法就是始终将深度较深的往上跳跃一步,直到u,v的深度第一次相等时,那么该节点就是lca(u,v)。但是这样做的话时间复杂度是\Theta (n)的,面对多组查询的情况,那就完蛋了。

其实,可以让两个节点同时上升,直到相遇为止。那么就要先把u和v置于同一层,将深层节点上移至与浅层节点相同的层数即可。此时上升层数\Delta depth=dep_{lca(u,v)}-dep_{u}=dep_{lca(u,v)}-dep_{v}(此时u和v处于同一层)。面对未知深度的lca(u,v),我们就需要枚举\Delta depth尝试。综合来看,枚举向上2^{i}步最佳。

那又出现了一个问题,调了2^{k}步之后,达到了u和v的公共祖先,但是这可能并不是n和v的最近公共祖先——跳“过去”了。为了避免这种情况,我们选择从大到小枚举,直到j==0跳出循环,若跳跃2^{i}步仍然不同则继续跳跃……

跳跃操作是通过动态规划实现的。用f[i][j]表示节点i的跳跃j步,可以由子问题推导出,容易看出f[i][j]=f[f[i][j-1]][j-1],可以预处理。

于是可以得到:

预处理:

void dfs(int u,int fa)
{dep[u]=dep[fa]+1;for(int i=head[u];i!=0;i=e[i].next){int j=e[i].v;if(j==fa)continue;f[j][0]=u;for(int k=1;(1<<k)<=dep[u];k++)f[j][k]=f[f[j][k-1]][k-1];dfs(j,u);}
}

lca的实现:

int lca(int x,int y)
{if(dep[x]<dep[y])swap(x,y);for(int i=19;i>=0;i--)if(dep[f[x][i]]>=dep[y])x=f[x][i];//将两点置于同一深度if(x==y)return y;//若两点相同,即为共同祖先,而此时深度为min(dep[x],dep[y]),所以为lca(u,v),直接返回for(int i=19;i>=0;i--)//枚举i,跳跃2^i步if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];//同步倍增return f[y][0];返回父亲节点(向上跳2^0=1层到达父节点)
}

这里的19是因为作者做的一道题节点数小等于500000个,19=\left \lceil {log_{2}}^{500000} \right \rceil。所以这里的数字就是\left \lceil {log_{2}}^{n} \right \rceil(n为节点总数)

此处用链式前向星(邻接表)存树,再次为大家奉上代码:

const int z=500010;
struct edge
{int v,next;
}e[2*z];
void addedge(int u,int v)
{idx++;e[idx].v=v;e[idx].next=head[u];head[u]=idx;
}//链式前向星

预处理时间复杂度是\Theta (n{log_{2}}^{n}),每次查询时间复杂度是\Theta ({log_{2}}^{n}),查询m次,则整体的时间复杂度是\Theta (n{log_{2}}^{n}+m{log_{2}}^{n}),对比暴力的\Theta (n^{2}+nm)确乎是快了不少!
上述算法主要有3个核心部分

(1)建立兄弟链表法的二叉树结构

(2)用DFS获得预处理数据

(3)使用倍增法查询LCA

推荐题目

[NOIP2014 提高组] 联合权值 - 洛谷

【模板】最近公共祖先(LCA) - 洛谷

[AHOI2008] 紧急集合 / 聚会 - 洛谷

专心OI - 找祖先 - 洛谷

Information Graph - 洛谷

1-Trees and Queries - 洛谷

这篇关于【最近公共祖先】浅论lca的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/846605

相关文章

poj1330(LCA最近公共祖先)

题意:求最近公共祖先 思路:之前学习了树链剖分,然后我就用树链剖分的一小部分知识就可以解这个题目了,记录每个结点的fa和depth。然后查找时,每次将depth大的结点往上走直到x = y。 代码如下: #include<iostream>#include<algorithm>#include<stdio.h>#include<math.h>#include<cstring>

最近心情有点复杂:论心态

一月一次的彷徨又占据了整个身心;彷徨源至不自信;而不自信则是感觉自己的价值没有很好的实现亦或者说是自己不认可自己的目前的生活和状态吧。 我始终相信一句话:任何人的生活形态完全是由自己决定的;外在的总归不能直达一个人的内心深处。所以少年 为了自己想要的生活 多坚持努力吧、不为别人只为自己心中的那一丝执着。 由此我看到了一个故事: 一个心情烦躁的人去拜访禅师。他问禅师:我这辈子就这么注定了吗?您

在二叉树中找到两个节点的最近公共祖先(基于Java)

如题  题解 public int lowestCommonAncestor(TreeNode root, int o1, int o2) {//记录遍历到的每个节点的父节点。Map<Integer, Integer> parent = new HashMap<>();Queue<TreeNode> queue = new LinkedList<>();parent.put(roo

【UVA】10066-The Twin Towers(最长公共子串问题)

赤裸裸的最长公共子串问题,没什么好说的,注意的是,每组数据后面都有一个空行。 13996019 10066 The Twin Towers Accepted C++ 0.015 2014-08-06 00:34:53 #include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using

算法图解(8~10贪心,动态规划,K最近邻算法)

贪心算法 在每一步都选择局部最优解,从而期望最终得到全局最优解。 贪心算法并不总能保证全局最优解,因此需要满足以下两个条件: 贪心选择性质:可以通过局部最优选择构造出全局最优解。最优子结构:问题的最优解包含其子问题的最优解。 实例:给定面额的硬币,用最少硬币凑出指定金额 int minCoins(vector<int>& coins, int amount) {int count = 0

救命!我已经彻底被最近的FLUX模型征服了

这是一期FLUX模型配套的罗拉模型推荐,这个大模型真的很香,尤其是对于人物的手部处理,推荐大家去尝试下,我已经爱上这个大模型了。 ① Flux魅影超模 https://www.liblib.art/modelinfo/15818662ba2e443d9b4f9a87c13fff55 关键词:照片上是一位优雅的年轻女子,一头棕色卷发,身穿米色上衣,戴着金耳环。她背对着相机,背景是浅色的。重点是

计算两个字符串的最大公共字符串的长度,字符不区分大小写

/*** */package testString;import java.util.Scanner;/***@author: Administrator*@date: 2016-12-28 下午01:08:30*/public class Main {public static void main(String[] args){Scanner sc=new Scanner(Syste

redis 实现单位时间内错误记录 时间到key值就被清除------最近脑子不好使觉得还是写个博客试试

直接在客户端操作的, 所以需要redis的简单命令  去对比JAVA客户端jedis的命令就行   添加---set     格式 set  key  value  EX time(秒)   如果这个time不添加的话 ,那默认就是 永久 获取--get    格式 get key  ---查看剩余时间    格式 TTL key ---实现key实现自增: inrc key

最近刚接触用到的一些linux命令(CentOS7的命令)二〇一八年十月三十日

linux  查看本地     ip  ip addr  查看本地系统     #cat /etc/issue 在CentOS下执行显示为: CentOS release 5.7 (Final) Kernel \r on an \m 或在Ubuntu下显示为: Ubuntu 11.04 \n \l 可以用来查看当前正在运行的 Ubuntu 的版本号。  查看系统内核     uname -a

Python高效计算两个字符串的最长公共子序列

Python高效计算两个字符串的最长公共子序列 在Python面试中,考官通常会关注候选人的编程能力、问题解决能力以及对Python语言特性的理解。计算两个字符串的最长公共子序列(Longest Common Subsequence, LCS)是一个经典的动态规划问题,广泛应用于文本比较、DNA序列分析等领域。本文将详细介绍如何编写一个函数,计算两个字符串的最长公共子序列,确保代码实用性强,条理