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离线算法的更多相关文章

  1. LCA离线算法Tarjan详解

    离线算法也就是需要先把所有查询给保存下来,最后一次输出结果. 离线算法是基于并查集实现的,首先就是初始化P[i] = i. 接下来对于每个点进行dfs: ①首先判断是否有与该点有关的查询,如果当前该点 ...

  2. poj1330+hdu2586 LCA离线算法

    整整花了一天学习了LCA,tarjan的离线算法,就切了2个题. 第一题,给一棵树,一次查询,求LCA.2DFS+并查集,利用深度优先的特点,回溯的时候U和U的子孙的LCA是U,U和U的兄弟结点的子孙 ...

  3. Closest Common Ancestors---poj1470(LCA+离线算法)

    题目链接:http://poj.org/problem?id=1470 题意是给出一颗树,q个查询,每个查询都是求出u和v的LCA:    以下是寻找LCA的预处理过程: void LCA(u){ f ...

  4. HDU 2874 Connections between cities(LCA离线算法实现)

    http://acm.hdu.edu.cn/showproblem.php?pid=2874 题意: 求两个城市之间的距离. 思路: LCA题,注意原图可能不连通. 如果不了解离线算法的话,可以看我之 ...

  5. 距离LCA离线算法Tarjan + dfs + 并查集

    距离B - Distance in the Tree 还是普通的LCA但是要求的是两个节点之间的距离,学到了一些 一开始我想用带权并查集进行优化,但是LCA合并的过程晚于离线计算的过程,所以路径长度会 ...

  6. LCA离线算法Tarjan的模板

    hdu 2586:题意:输入n个点的n-1条边的树,m组询问任意点 a b之间的最短距离 思路:LCA中的Tarjan算法,RMQ还不会.. #include <stdio.h> #inc ...

  7. HDU 2874 LCA离线算法 tarjan算法

    给出N个点,M条边.Q次询问 Q次询问每两点之间的最短距离 典型LCA 问题   Marjan算法解 #include "stdio.h" #include "strin ...

  8. POJ1986 DistanceQueries 最近公共祖先LCA 离线算法Tarjan

    这道题与之前那两道模板题不同的是,路径有了权值,而且边是双向的,root已经给出来了,就是1,(这个地方如果还按之前那样来计算入度是会出错的.数据里会出现多个root...数据地址可以在poj的dis ...

  9. 1128 - Greatest Parent---LightOj(LCA+离线算法)

    题目链接:http://lightoj.com/volume_showproblem.php?problem=1128 给你一颗树,树的每个节点都有一个权值,树根是节点0,权值为1,树中每个节点的权值 ...

随机推荐

  1. Spring-Kafka 2.0.0发送API翻译

    Kafka Template–2.2.0 api KafkaTemplate KafkaTemplate这个类包装了个生产者,来提供方便的发送数据到kafka的topic里面. 同步和异步的方法都有, ...

  2. MyEclipse Tern was unable to complete your request in time

    1.错误描述 2.错误原因 由错误提示可知,是由于MyEclipse Tern不能及时完成回复 3.解决办法 (1)Window--->Preferences--->MyEclipse-- ...

  3. golang包快速生成base64验证码

    base64Captcha快速生成base64编码图片验证码字符串 支持多种样式,算术,数字,字母,混合模式,语音模式. Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一.Base6 ...

  4. sublime上配置markdown

    等等等等 简书一个不错的教程:Sublime Text3的Markdown配置 补充说明:第一步可以直接找 Tools-->install package control. ^.^ ...

  5. matlab中hdl coder 的使用

    今天摸索了一下hdl coder的使用方法,各个步骤主要是照猫画虎,有些地方还是不理解,先总结一下: 1.要想调用quartus或者Xilinx综合布局布线需要先设置,设置的方法有两种,命令窗口输入 ...

  6. 转载FPGA学习之内嵌乘法器调用

    补充一点,除法的时候如果直接a/b那么就会调用lpm模块,不管输入是否是常数,乘法的时候输入都是reg型变量会调用硬件乘法器,有一个是常数就会调用lpm模块. 上课的时候一直听老师说真正实践的时候你别 ...

  7. python 函数名,闭包

    1.函数名字的应用 函数名是什么? 函数名是函数的名字,本质:变量,特殊变量 函数名+() ———>执行此函数: 2.函数名的赋值: def func2(): print(44) f = fun ...

  8. numpy之初探排序和集合运算

    排序 排序 numpy与python列表内置的方法类似,也可通过sort方法进行排序. 用法如下: In [1]: import numpy as np In [2]: x = np.random.r ...

  9. Browserify使用指南(转)

    让浏览器加载Nodejs模块 目前NPM上有二十多万个NodeJS模块,它们都是通过CMD的方式打包的,除了特定的可以使用CMD模块加载器加载的模块,大部分nodejs模块无法直接使用到浏览器环境中. ...

  10. SQL语言分为五大类

    SQL语言分为五大类:DDL(数据定义语言) - Create.Alter.Drop 这些语句自动提交,无需用Commit提交.DQL(数据查询语言) - Select 查询语句不存在提交问题.DML ...