Tarjan的LCA离线算法
LCA(Least Common Ancestors)是指树结构中两个结点的最低的公共祖先。而LCA算法则是用于求两个结点的LCA。当只需要求一对结点的LCA时,我们很容易可以利用递归算法在O(n)的时间复杂度内实现,其中n为树中的结点数目。但是有时候我们会要求计算多组结点对各自的LCA,这样总的时间复杂度将会到达O(nq),其中q为问题总数,这往往是无法接受的。
LCA离线算法用于计算一组预先给出的结点对各自的LCA(即我们允许在拥有所有结点对信息之后再进行解答)。Tarjan算法即是一种LCA离线算法。我们需要为结点维护一下属性:实现并查集所需要的属性p和rank,children用于保存所有直接孩子结点,ancestor用于记录结点的祖先,visited用于记录结点是否已经访问过,questionList用于记录与node相关的LCA问题。
tarjan(node)
for child in node.children
tarjan(child)
union(child, node)
node.findSet().ancestor = node
node.visited = true
for question in node.questionList
other = another node specified by question //将other设定为question中涉及到的另外一个问题
if(other.visited)
question.answer = other.findSet().ancestor
上面就是LCA的所有部分了。其中union和findSet分别用于合并并查集以及查找结点所在并查集的代表结点。要解决所有的LCA问题,只需要用树中的根结点调用tarjan函数即可。
先说明时间复杂度,由于并查集的所有操作摊还代价都可以视作为O(1),因此tarjan函数的2~6行实际上就是一个普通的深度优先搜索而已,其时间复杂度为O(n)。而第7~10行每次循环都会扫描一个问题,且每个问题只涉及两个结点,故最多只会被扫描两次,因此只会被调用O(q)次,故tarjan函数总共花费的时间复杂度为O(n+q),这无疑是优秀的时间复杂度。
再说明算法的正确性:只需要说明每个问题都被正确求解了。从两方面说明,1.每个问题的answer属性都被设置过.2.每次对问题的answer属性进行设置时,其值总是正确的。
由于每个问题都会被扫描两次,在第一次扫描结束后,之后会执行第6行将结点设置为已访问。而在第二次扫描时,发现另外一个结点已经被访问过了,因此会执行第10行代码,对answer属性进行设置。因此我们保证每个问题都会被解答,且以第二次扫描时的答案为最终答案。因此方面1被成功证明。
假设问题Q问的是结点u和v的LCA,并且假设其LCA为a。分三种情况讨论,1是u=v,2是u!=v=a,3是u、v、a三者均不同。当情况1发生时,u在对所有孩子递归完后,扫描涉及自身的问题时,会将问题解决两次,而每次都将答案设置为自身,故这种情况下赋值是正确的。当第2种情况发生时,我们在v对孩子进行递归完毕后,会将所有孩子都合并到v所在的集合中,并将v所在集合的代表结点的祖先设置为v。而之后扫描问题时会遇到Q,此时由于u已经被访问过了,会将答案设置为v,此时答案是正确的(并且对该问题的第二次扫描已经完成,问题不会被重复赋值)。对于情况3,不妨设u在v之前被访问。由于a是u和v的LCA,因此a的任意子结点都不可能是u和v的LCA,即u和v挂在a的两个不同的子结点下。故当我们访问完u,并回溯到a时,会将u加入到a所在集合,并将a所在集合的祖先设置为a。而之后搜索到v后,v在扫描到Q时,会将Q的answer值设置为u.findSet().ancestor,此时u依旧处于a所在集合中,而a所在集合的祖先始终为a(因为第4~5行代码只会将以当前结点为根的子树中的结点加入到自身所代表的集合中,而由于a的流程尚未走完),因此answer为a,答案正确。
Tarjan的LCA离线算法的更多相关文章
- LCA离线算法Tarjan详解
离线算法也就是需要先把所有查询给保存下来,最后一次输出结果. 离线算法是基于并查集实现的,首先就是初始化P[i] = i. 接下来对于每个点进行dfs: ①首先判断是否有与该点有关的查询,如果当前该点 ...
- poj1330+hdu2586 LCA离线算法
整整花了一天学习了LCA,tarjan的离线算法,就切了2个题. 第一题,给一棵树,一次查询,求LCA.2DFS+并查集,利用深度优先的特点,回溯的时候U和U的子孙的LCA是U,U和U的兄弟结点的子孙 ...
- Closest Common Ancestors---poj1470(LCA+离线算法)
题目链接:http://poj.org/problem?id=1470 题意是给出一颗树,q个查询,每个查询都是求出u和v的LCA: 以下是寻找LCA的预处理过程: void LCA(u){ f ...
- HDU 2874 Connections between cities(LCA离线算法实现)
http://acm.hdu.edu.cn/showproblem.php?pid=2874 题意: 求两个城市之间的距离. 思路: LCA题,注意原图可能不连通. 如果不了解离线算法的话,可以看我之 ...
- 距离LCA离线算法Tarjan + dfs + 并查集
距离B - Distance in the Tree 还是普通的LCA但是要求的是两个节点之间的距离,学到了一些 一开始我想用带权并查集进行优化,但是LCA合并的过程晚于离线计算的过程,所以路径长度会 ...
- LCA离线算法Tarjan的模板
hdu 2586:题意:输入n个点的n-1条边的树,m组询问任意点 a b之间的最短距离 思路:LCA中的Tarjan算法,RMQ还不会.. #include <stdio.h> #inc ...
- HDU 2874 LCA离线算法 tarjan算法
给出N个点,M条边.Q次询问 Q次询问每两点之间的最短距离 典型LCA 问题 Marjan算法解 #include "stdio.h" #include "strin ...
- POJ1986 DistanceQueries 最近公共祖先LCA 离线算法Tarjan
这道题与之前那两道模板题不同的是,路径有了权值,而且边是双向的,root已经给出来了,就是1,(这个地方如果还按之前那样来计算入度是会出错的.数据里会出现多个root...数据地址可以在poj的dis ...
- 1128 - Greatest Parent---LightOj(LCA+离线算法)
题目链接:http://lightoj.com/volume_showproblem.php?problem=1128 给你一颗树,树的每个节点都有一个权值,树根是节点0,权值为1,树中每个节点的权值 ...
随机推荐
- 你所不知道的 Java 之 HashCode
之所以写HashCode,是因为平时我们总听到它.但你真的了解hashcode吗?它会在哪里使用?它应该怎样写? 相信阅读完本文,能让你看到不一样的hashcode. 使用hashcode的目的在于: ...
- Python虚拟环境设置
Python2环境 首先,我们用pip安装virtualenv: pip3 install virtualenv 然后,假定我们要开发一个新的项目,需要一套独立的Python运行环境,可以这么做: 第 ...
- css3单冒号和双冒号的区别
css3中对于伪元素的使用,在项目开发中使用得当将会对代码的可读性又很大的提升.但是对于伪类大家或许都知道是一些选择器的使用,这里总结了关于伪元素中单冒号和双冒号的区别: 再官方定义中规定单冒号都为伪 ...
- 转载 matlab矩阵数组常用操作
一. length 返回矩阵最长维的的长度 ndims 返回维数 numel 返回矩阵元素个数size ...
- 十一、python沉淀之路--map函数、filter函数、reduce函数、匿名函数、内置函数
一.map函数 1.自定义函数,实现类似于map函数的功能 num_l = [1,3,4,5,6,9] def power(n): return n ** 2 def map_test(func,ar ...
- C#获取文件的MD5码
using System; using System.IO; using System.Security.Cryptography; using System.Text; namespace Send ...
- Java 程序员容易犯的10个SQL错误
Java程序员编程时需要混合面向对象思维和一般命令式编程的方法,能否完美的将两者结合起来完全得依靠编程人员的水准: 技能(任何人都能容易学会命令式编程) 模式(有些人用“模式-模式”,举个例子,模式可 ...
- memcache使用经历
memcache可以看成是一个大内存库,因为可以集群,所以可以看成是无限大的内存库.既然是内存库,那么我们放的就是一些map了.map里就键值对,虽然内存可以通过集群的方式无限扩展,但单个键值对的大小 ...
- linux 目标文件 bss,data,text,rodata,堆,栈***
linux目标文件 一个简单的程序被编译成目标文件后的结构如下: 从图可以看出,已初始化的全局变量和局部静态变量保存在 .data段中,未初始化的全局变量和未初始化的局部静态变量保存在 .bss段中. ...
- innodb事务日志详解
首先看InnoDB的缓存和文件的关系图如下: InnoDB事务日志功能介绍 InnoDB使用日志来减少提交事务时的开销.因为日志中已经记录了事务,就无须在每个事务提交时把缓冲池的脏块刷新(flush) ...