关于离线算法
(下面内容可以略过。)
离线算法其实就是将多个询问一次性解决。离线算法往往是与在线算法相对的。例如求LCA的算法中,树上倍增属于在线算法,在对树进行O(n)O(n)O(n)预处理后,每个询问用O(log2n)O(log_2n)O(log2​n)复杂度回答。而离线的Tarjan算法则是用O(n+q)O(n+q)O(n+q)时间将询问一次性全部回答。

详解
下面是一棵树,我们将以这棵树为例子讲解Tarjan算法,其中0号点为根。

假设对于这棵树的询问有4个,分别询问:
LCA(2,8)LCA(2,8)LCA(2,8)
LCA(5,6)LCA(5,6)LCA(5,6)
LCA(2,5)LCA(2,5)LCA(2,5)
LCA(4,9)LCA(4,9)LCA(4,9)
首先我们将这四个询问顺序调转,再复制四份,现在就有8个询问:
LCA(2,8)LCA(2,8)LCA(2,8)
LCA(5,6)LCA(5,6)LCA(5,6)
LCA(2,5)LCA(2,5)LCA(2,5)
LCA(4,9)LCA(4,9)LCA(4,9)
LCA(8,2)LCA(8,2)LCA(8,2)
LCA(6,5)LCA(6,5)LCA(6,5)
LCA(5,2)LCA(5,2)LCA(5,2)
LCA(9,4)LCA(9,4)LCA(9,4)
这一步是必须的,后面将会说明它。
然后对于每个节点u,给它开一个链表,找到所有的询问 LCA(u,v)LCA(u,v)LCA(u,v) ,把v插入到u的链表后,同时把询问编号插入,以便按照输入顺序输出答案。
于是询问就被离线了。
那么到底怎么求LCA呢?我们对带着询问树进行一次dfs。如图:
第1步,0号点被遍历:

没有与0相关的询问,继续dfs。
第2步,1号点被遍历:

没有与1相关的询问,继续dfs。
第3步,2号点被遍历:

2号点没有儿子了,与2相关的询问有 LCA(2,5)LCA(2,5)LCA(2,5) 和 LCA(2,8)LCA(2,8)LCA(2,8) 。
但是5号点和8号点都还没有遍历过,我们什么也不知道,因此这两个询问不理它。
第4步,2号点回溯(遍历完毕并回溯的点标为蓝色):

第5步,3号点被遍历:

没3号点的事,继续dfs。
第6步,4号点被遍历:

关于4号点的询问我们也是一无所知,回溯。
第7步,4号点回溯:

第8步,5号点被遍历:

关于5的询问有 LCA(5,6)LCA(5,6)LCA(5,6) 和 LCA(5,2)LCA(5,2)LCA(5,2) 。
6号点的信息我们还不知道,但是2号点,我们已经知道它已经被访问且回溯了。
5的祖先一定在当前正在访问的节点中(也就是访问了还没回溯的点),那么
LCA(5,2)LCA(5,2)LCA(5,2) 其实也就是在图上红色的节点里找出满足如下两个条件的点:
1.它是2的祖先。
2.它深度最大。
很容易发现这个点就是1,于是这里就可以记录下来 LCA(5,2)=1LCA(5,2)=1LCA(5,2)=1 。
第9步,5号点回溯:

第10步,3号点回溯:

第11步,6号点被遍历:

还是跟之前一样,对于跟6号点有关的询问 LCA(6,5)LCA(6,5)LCA(6,5) ,去找红色点里深度最大的5的祖先,显然就是1,记下 LCA(6,5)=1LCA(6,5)=1LCA(6,5)=1。
第12步,6号点回溯。

第13步,1号点回溯:

第14步,7号点被遍历:

第15步,8号点被遍历:

按照之前做法,在红色节点里找出深度最大的2的祖先,可以求出 LCA(8,2)=0LCA(8,2)=0LCA(8,2)=0 。
第16步,8号点回溯:

第17步,9号点被遍历:

显然了,LCA(9,4)=0LCA(9,4)=0LCA(9,4)=0 。
后面的过程就略过,因为至此我们已经求出了四个询问的答案。
LCA(2,8)=0LCA(2,8)=0LCA(2,8)=0
LCA(5,6)=1LCA(5,6)=1LCA(5,6)=1
LCA(2,5)=1LCA(2,5)=1LCA(2,5)=1
LCA(4,9)=0LCA(4,9)=0LCA(4,9)=0
也许你已经明白了,为什么要把LCA(u,v)LCA(u,v)LCA(u,v)复制一份LCA(v,u)LCA(v,u)LCA(v,u),因为在上面过程中,我们不能保证遍历u时v已经回溯,因此需要复制一个询问。
上面的过程已经可以离线求出LCA了,但复杂度不是最优的,问题就出在上面找“红色节点中u的深度最大的祖先”,如果从u点一步步向上跳,复杂度为O(nq)O(nq)O(nq)。
假如对于一个询问LCA(u,v)LCA(u,v)LCA(u,v),u已经被遍历过,此时遍历到v。容易发现LCA(u,v)LCA(u,v)LCA(u,v)一定是红色的(也就是访问了还未回溯)。那么如果我们在dfs的过程中,在节点u的儿子遍历完毕回溯时,将儿子的fa指向点u,那么对于询问LCA(u,v)LCA(u,v)LCA(u,v),只需要从u开始,不断往u的父亲跳,跳到的深度最小一个节点,就是LCA(u,v)LCA(u,v)LCA(u,v)。
怎么去证明呢?首先其必是u的祖先,这个不用说。但为什么是深度最小的那一个呢?不是要求深度最大的吗?因为我们是在回溯时将u的fa指向它的父亲的,如果深度不是最小,则u的这个祖先的子树里肯定没有v。如果有v的话,其必然是深度最小的那一个。由于u已访问完毕,而v还在访问中,因此u的父亲里不会有比LCA(u,v)LCA(u,v)LCA(u,v) 深度更大的点,此时就能保证u的fa里深度最小的那个就是LCA(u,v)LCA(u,v)LCA(u,v)
“将儿子的父亲指向点u”这个操作用并查集完成,可以保证在常数复杂度。因此对树进行遍历需要O(n)O(n)O(n)复杂度,而总共有q个询问,每个询问可以O(1)O(1)O(1)回答,复杂度为O(n+q)O(n+q)O(n+q)。

在此奉上代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+20;
int ver[N],Next[N],head[N],tot,vis[N],f[N],ans[N],n,m,root;
struct aaa{int id,y;
};
vector < aaa > q[N];
void add(int x,int y)
{ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
}
int get(int x)
{if(x==f[x]) return x;return f[x]=get(f[x]);
}
void trajan(int x)
{vis[x]=1;for(int i=head[x];i;i=Next[i]){int y=ver[i];if(vis[y]) continue;trajan(y);f[y]=x;}for(int i=0;i<q[x].size();i++){int id=q[x][i].id,y=q[x][i].y;if(vis[y]==2) ans[id]=get(y);}vis[x]=2;
}
int main()
{ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);cin>>n>>m>>root;for(int i=1;i<=n;i++) f[i]=i;int x,y;for(int i=1;i<n;i++) cin>>x>>y,add(x,y),add(y,x);for(int i=1;i<=m;i++){cin>>x>>y;if(x==y){ans[i]=x;continue;} q[x].push_back({i,y}),q[y].push_back({i,x});}trajan(root);for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';return 0;
}

trajan算法求lca 超级详细配图讲解相关推荐

  1. 超级详细树讲解三 —— B树、B+树图解+代码

    首先很高兴你看到了这篇文章,这篇文章可能会花费你很长很长的时间去看,但是这篇文章包括的内容绝对足够你对树的一个系统性的学习.为什么要写这篇文字呢?因为自己在学习树的时候,有些博客只有图解,有些博客只有 ...

  2. matlab中识别过程训练,人脸识别PCA算法matlab实现及详细步骤讲解

    %FaceRec.m %PCA人脸识别修订版,识别率88% %calc xmean,sigma and its eigen decomposition allsamples=[];%所有训练图像 fo ...

  3. QSPI DSPI SPI超级详细的讲解,友好的讲解。

    外扩的flash接口一般都是SPI(与片上flash不一样,片上flash理解为在已经集成在芯片内部了,集成在芯片内部的flash就一般不存在SPI接口说法了,都是通过总线进行数据的交换.而外扩的fl ...

  4. POJ 1986:Distance Queries(倍增求LCA)

    http://poj.org/problem?id=1986 题意:给出一棵n个点m条边的树,还有q个询问,求树上两点的距离. 思路:这次学了一下倍增算法求LCA.模板. dp[i][j]代表第i个点 ...

  5. 【数据结构】最小瓶颈路 加强版(Kruskal重构树RMQ求LCA)

    题目描述 给定一个 n 个点 m 条边的无向连通图,编号为 1 到 n ,没有自环,可能有重边,每一条边有一个正权值 w . 给出 q 个询问,每次给出两个不同的点 u 和 v ,求一条从 u 到 v ...

  6. Tarjan算法求割点与割边(python3实现)

    from typing import List, Tuple''' Trajan算法求无向图的桥 '''class Tarjan:# 求无向连通图的桥@staticmethoddef getCutti ...

  7. 树上问题(一)倍增算法求最近公共祖先

    倍增算法求最近公共祖先 一.概述 在图论和计算机科学中,最近公共祖先 LCA(Least Common Ancestors)是指在一个树或者有向无环图中同时拥有v和w作为后代的最深的节点.在这里,我们 ...

  8. NLP之WE之CBOWSkip-Gram:CBOWSkip-Gram算法概念相关论文、原理配图、关键步骤之详细攻略

    NLP之WE之CBOW&Skip-Gram:CBOW&Skip-Gram算法概念相关论文.原理配图.关键步骤之详细攻略 目录 CBOW&Skip-Gram算法相关论文 CBOW ...

  9. uni-app - 最详细 H5 网页接入微信登录功能,提供公众号配置与详细注释示例代码(移动端网页实现点击登录按钮后 调用微信公众号授权登录功能 详细讲解接入流程与详细示例代码)官方最新超级详细教程

    前言 关于 uni-app 项目中接入微信授权登录的文章鱼龙混杂,各种乱代码.过时.没注释.不讲流程原理,非常难用. 本文实现了 uni-app H5 移动端网页项目,实现微信授权登录功能,详细讲解接 ...

  10. 全卷积网络FCN详细讲解(超级详细哦)

    原文链接:https://blog.csdn.net/qq_41760767/article/details/97521397?depth_1-utm_source=distribute.pc_rel ...

最新文章

  1. boost::asio::streambuf 基本用法和注意事项
  2. nodejs笔记-异步编程
  3. python PyQt5 QtWidgets.QAbstractSlider.valueChanged()
  4. Python—实训day3—简单的在线翻译程序
  5. 成长 | 《大厂晋升指南》学习总结(中)
  6. 管道(Pipe)/createPipe
  7. 实现if_数组实现固定栈和队列+栈与队列相互实现
  8. php从头部添加,php如何向header头添加Authorization信息?
  9. 手动测试是进入测试自动化之前的重要阶段
  10. 推荐系列:2008年第05期 总7期
  11. 3月22 关于CSS
  12. 系列1—BabeLua入门
  13. 查询 JetsonNano I2C 的工作频率(波特率)
  14. 秋天是一个思念的季节
  15. Ubuntu18.08安装到移动硬盘(UFEI引导)
  16. pta 天梯赛 7-3 换硬币 (20 分)day4
  17. 吃粽子html5游戏,端午先玩个小游戏,再去吃粽子吧~
  18. 80年代的年画,画面朝气蓬勃,催人奋进,正能量满满,展望新时代
  19. 岛国电影生物科普就是强—生命大跃进
  20. 《秒懂EXCEL》重点复习笔记01

热门文章

  1. H5社交漂流瓶交友源码/附安装教程
  2. 如何用Docker搭建自己的LANP|LNMP环境?
  3. android模拟器录制脚本,什么安卓模拟器可实现操作录制?MuMu模拟器成为你的按键精灵_MuMu安卓模拟器/MuMu手游助手...
  4. 浅谈C# 多态的魅力(虚方法,抽象,接口实现)
  5. 宁夏移民文化的四个特点
  6. AXI4总线协议总结
  7. Jvm面试题及答案(2021年Jvm面试题大全带答案)
  8. 手机浏览器能上网微信无法连接服务器,手机打不开微信的网页怎么办?手机打不开微信网页的原因和解决方法...
  9. MySQL窗口函数(分组内排序、筛选)
  10. 什么是机器翻译?(科普向)