Tarjan求LCA
来源:互联网 发布:mac上绘画软件 编辑:程序博客网 时间:2024/05/29 14:34
我对Tarjan越来越崇拜了!
首先是最近公共祖先的概念(什么是最近公共祖先?):
在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点。
换句话说,就是两个点在这棵树上距离最近的公共祖先节点。
所以LCA主要是用来处理当两个点仅有唯一一条确定的最短路径时的路径。
有人可能会问:那他本身或者其父亲节点是否可以作为祖先节点呢?
答案是肯定的,很简单,按照人的亲戚观念来说,你的父亲也是你的祖先,而LCA还可以将自己视为祖先节点。
来看下面这张图
LCA(1,2)=1
LCA(2,4)=1
而对于求LCA的问题,有三种方法
1:倍增求LCA
2:DFS序+RMQ
3:Tarjan
前两种都使用了倍增的思想,而且是在线算法!
Tarjan是一种离线算法,复杂度O(n+m)n为点的数量,m查询的数量。
对于查询比较少的问题,Tarjan算法比较有优势。
图1为Tarjan算法(不使用快读)
图2为倍增算法(使用快读)
可以很明显的看出Tarjan的优势.
下面是具体实现
总思路就是每进入一个节点u的深搜,就把整个树的一部分看作以节点u为根节点的小树,再搜索其他的节点。每搜索完一个点后,如果该点和另一个已搜索完点为需要查询LCA的点,则这两点的LCA为另一个点的现在的祖先。
1.先建立两个链表,一个为树的各条边,另一个是需要查询最近公共祖先的两节点。
2.建好后,从根节点开始进行一遍深搜。
3.先把该节点u的father设为他自己(也就是只看大树的一部分,把那一部分看作是一棵树),搜索与此节点相连的所有点v,如果点v没被搜索过,则进入点v的深搜,深搜完后把点v的father设为点u。
4.深搜完一点u后,开始判断节点u与另一节点v是否满足求LCA的条件,满足则将结果存入数组中。
5.搜索完所有点,自动退出初始的第一个深搜,输出结果。
如上图,根据实现算法可以看出,只有当某一棵子树全部遍历处理完成后,才将该子树的根节点标记为有颜色(初始化是白色),假设程序按上面的树形结构进行遍历,首先从节点1开始,然后递归处理根为2的子树,当子树2处理完毕后,节点2, 5, 6均为红色;接着要回溯处理3子树,首先被染色的是节点7(因为节点7作为叶子不用深搜,直接处理),接着节点7就会查看所有询问(7, x)的节点对,假如存在(7, 5),因为节点5已经被染黑,所以就可以断定(7, 5)的最近公共祖先就是find(5),即节点1(因为2子树处理完毕后,子树2和节点1返回了合并后的树的根1,此时树根的祖先的值就是1)。
luogu模板题
实现代码
#include <cstdio>#include <iostream>using namespace std;const int maxm=501000;struct node{ int net; int to; int lca;};bool vis[maxm];int fat[maxm];node edge[2][2*maxm];int cnt[2],head[2][maxm];void add(int id,int x,int y){ edge[id][++cnt[id]].to=y; edge[id][cnt[id]].net=head[id][x]; head[id][x]=cnt[id];}int find(int x){ if(fat[x]==x) return x; else return fat[x]=find(fat[x]);}void dfs(int x){ vis[x]=1; fat[x]=x;//以x为根节点的子树 for(int K=head[0][x];K;K=edge[0][K].net) { int p=edge[0][K].to; if(vis[p]) continue; dfs(p); fat[p]=x; } for(int i=head[1][x];i;i=edge[1][i].net) { int p=edge[1][i].to; if(!vis[p]||edge[1][i].lca) continue; edge[1][i].lca=find(p);//找LCA if(i%2) edge[1][i+1].lca=edge[1][i].lca; else edge[1][i-1].lca=edge[1][i].lca; }}int main(){ int n,m,p; scanf("%d%d%d",&n,&m,&p); for(int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); add(0,x,y); add(0,y,x); } for(int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); add(1,x,y),add(1,y,x);//一定要存双向的,因为你不知道那个节点先访问到 } dfs(p);//根节点 for(int i=1;i<=m;i++) printf("%d\n",edge[1][2*i].lca);}
- tarjan求lca
- tarjan离线求lca
- tarjan求LCA
- 图论--tarjan求lca
- tarjan求LCA
- Tarjan求LCA
- tarjan求LCA笔记
- Tarjan求lca
- bsoj1375 tarjan离线求lca
- bsoj 2701 tarjan求lca
- 学习笔记:tarjan求lca
- tarjan算法 离线求LCA
- 浅谈Tarjan算法求LCA
- dfs(tarjan)求lca
- Tarjan离线算法求LCA小结
- tarjan 离线求 lca (专题)
- HDU 2460 Network 求桥(tarjan)+LCA
- poj 3728 The merchant(tarjan求lca)
- Xcode 中放置图片的位置,Images.xcassets 和直接放在源文件目录中的区别
- Pycharm清除波浪线
- 关于response.getWriter().write()返回int类型给前台的乱码问题
- 容器加載Web工程的Web.xml文件介紹
- ZooKeepr日志清理
- Tarjan求LCA
- px em rem,三者有什么区别,又各自有什么优劣呢?
- navicat for mysql注册码
- Java 学习总结
- 从windows 全新安装 到ubuntu 16.04 64位 (光盘)
- 语音识别使用推荐(讯飞、百度、腾讯、云知声等)
- 二叉树专题-lintcode递归与非递归遍历
- Mybatis级联查询和缓存
- c++:ASAP和ALAP算法