来自:http://www.cnblogs.com/ylfdrib/archive/2010/11/03/1867901.html

对于一棵有根树,就会有父亲结点,祖先结点,当然最近公共祖先就是这两个点所有的祖先结点中深度最大的一个结点。

0

|

1

/   \

2      3

比如说在这里,如果0为根的话,那么1是2和3的父亲结点,0是1的父亲结点,0和1都是2和3的公共祖先结点,但是1才是最近的公共祖先结点,或者说1是2和3的所有祖先结点中距离根结点最远的祖先结点。

在求解最近公共祖先为问题上,用到的是Tarjan的思想,从根结点开始形成一棵深搜树,非常好的处理技巧就是在回溯到结点u的时候,u的子树已经遍历,这时候才把u结点放入合并集合中,这样u结点和所有u的子树中的结点的最近公共祖先就是u了,u和还未遍历的所有u的兄弟结点及子树中的最近公共祖先就是u的父亲结点。以此类推。。这样我们在对树深度遍历的时候就很自然的将树中的结点分成若干的集合,两个集合中的所属不同集合的任意一对顶点的公共祖先都是相同的,也就是说这两个集合的最近公共最先只有一个。对于每个集合而言可以用并查集来优化,时间复杂度就大大降低了,为O(n + q),n为总结点数,q为询问结点对数。

另外Tarjan解法,是一个离线算法,就是说它必须将所有询问先记录下来,再一次性的求出每个点对的最近公共祖先,只有这样才可以达到降低时间复杂度。另外还有一个在线算法,有待学习。

伪代码:

//parent为并查集,FIND为并查集的查找操作
//QUERY为询问结点对集合
//TREE为基图有根树
Tarjan(u)
visit[u] = true
for each (u, v) in QUERY
if visit[v]
ans(u, v) = FIND(v)
for each (u, v) in TREE
if !visit[v]
Tarjan(v)
parent[v] = u

经典例题:

hdu2586 How far away ?

这道题题意是,给定一棵树,每条边都有一定的权值,q次询问,每次询问某两点间的距离。这样就可以用LCA来解,首先找到u, v 两点的lca,然后计算一下距离值就可以了。这里的计算方法是,记下根结点到任意一点的距离dis[],这样ans = dis[u] + dis[v] - 2 * dis[lca(v, v)]了,这个表达式还是比较容易理解的。。

//============================================================================
// Name : hdu2586.cpp
// Author : birdfly
// Description : 最近公共祖先
//============================================================================ #include <iostream>
#include <string.h>
#include <stdio.h>
#define NN 40002 // number of house
#define MM 202 // number of query
using namespace std; typedef struct node{
int v;
int d;
struct node *nxt;
}NODE; NODE *Link1[NN];
NODE edg1[NN * ]; // 树中的边 NODE *Link2[NN];
NODE edg2[NN * ]; // 询问的点对 int idx1, idx2, N, M;
int res[MM][]; // 记录结果,res[i][0]: u res[i][1]: v res[i][2]: lca(u, v)
int fat[NN];
int vis[NN];
int dis[NN]; void Add(int u, int v, int d, NODE edg[], NODE *Link[], int &idx){
edg[idx].v = v;
edg[idx].d = d;
edg[idx].nxt = Link[u];
Link[u] = edg + idx++; edg[idx].v = u;
edg[idx].d = d;
edg[idx].nxt = Link[v];
Link[v] = edg + idx++;
} int find(int x){ // 并查集路径压缩
if(x != fat[x]){
return fat[x] = find(fat[x]);
}
return x;
} void Tarjan(int u){
vis[u] = ;
fat[u] = u; for (NODE *p = Link2[u]; p; p = p->nxt){
if(vis[p->v]){
res[p->d][] = find(p->v); // 存的是最近公共祖先结点
}
} for (NODE *p = Link1[u]; p; p = p->nxt){
if(!vis[p->v]){
dis[p->v] = dis[u] + p->d;
Tarjan(p->v);
fat[p->v] = u;
}
}
}
int main() {
int T, i, u, v, d;
scanf("%d", &T);
while(T--){
scanf("%d%d", &N, &M); idx1 = ;
memset(Link1, , sizeof(Link1));
for (i = ; i < N; i++){
scanf("%d%d%d", &u, &v, &d);
Add(u, v, d, edg1, Link1, idx1);
} idx2 = ;
memset(Link2, , sizeof(Link2));
for (i = ; i <= M; i++){
scanf("%d%d", &u, &v);
Add(u, v, i, edg2, Link2, idx2);
res[i][] = u;
res[i][] = v;
} memset(vis, , sizeof(vis));
dis[] = ;
Tarjan(); for (i = ; i <= M; i++){
printf("%d\n", dis[res[i][]] + dis[res[i][]] - * dis[res[i][]]);
}
}
return ;
}

最近公共祖先 LCA Tarjan算法的更多相关文章

  1. 最近公共祖先LCA(Tarjan算法)的思考和算法实现

    LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了f ...

  2. 最近公共祖先LCA(Tarjan算法)的思考和算法实现——转载自Vendetta Blogs

    LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了f ...

  3. P5836 [USACO19DEC]Milk Visits S 从并查集到LCA(最近公共祖先) Tarjan算法 (初级)

    为什么以它为例,因为这个最水,LCA唯一黄题. 首先做两道并查集的练习(估计已经忘光了).简单来说并查集就是认爸爸找爸爸的算法.先根据线索理认爸爸,然后查询阶段如果发现他们的爸爸相同,那就是联通一家的 ...

  4. 最近公共祖先LCA Tarjan 离线算法

    [简介] 解决LCA问题的Tarjan算法利用并查集在一次DFS(深度优先遍历)中完成所有询问.换句话说,要所有询问都读入后才开始计算,所以是一种离线的算法. [原理] 先来看这样一个性质:当两个节点 ...

  5. POJ 1330 LCA最近公共祖先 离线tarjan算法

    题意要求一棵树上,两个点的最近公共祖先 即LCA 现学了一下LCA-Tarjan算法,还挺好理解的,这是个离线的算法,先把询问存贮起来,在一遍dfs过程中,找到了对应的询问点,即可输出 原理用了并查集 ...

  6. 图论-最近公共祖先-离线Tarjan算法

    有关概念: 最近公共祖先(LCA,Lowest Common Ancestors):对于有根树T的两个结点u.v,最近公共祖先表示u和v的深度最大的共同祖先. Tarjan是求LCA的离线算法(先存储 ...

  7. 最近公共祖先 LCA 倍增算法

          树上倍增求LCA LCA指的是最近公共祖先(Least Common Ancestors),如下图所示: 4和5的LCA就是2 那怎么求呢?最粗暴的方法就是先dfs一次,处理出每个点的深度 ...

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

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

  9. POJ 1986 Distance Queries (最近公共祖先,tarjan)

    本题目输入格式同1984,这里的数据范围坑死我了!!!1984上的题目说边数m的范围40000,因为双向边,我开了80000+的大小,却RE.后来果断尝试下开了400000的大小,AC.题意:给出n个 ...

随机推荐

  1. JVM内存管理之垃圾搜集器参数精解

    本文是GC相关的最后一篇,这次LZ只是罗列一下hotspot JVM中垃圾搜集器相关的重点参数,以及各个参数的解释.废话不多说,这就开始. 垃圾搜集器文章传送门 JVM内存管理------JAVA语言 ...

  2. 杂项-DB:内存数据库

    ylbtech-杂项-DB:内存数据库 内存数据库,顾名思义就是将数据放在内存中直接操作的数据库.相对于磁盘,内存的数据读写速度要高出几个数量级,将数据保存在内存中相比从磁盘上访问能够极大地提高应用的 ...

  3. pyc是什么

    python是解释型语言,需要解释器对程序逐行做出解释,然后直接运行. C语言是编译型语言,PC不需要翻译,直接执行就可以了. java也是解释型语言,不过速度可以跟编译型媲美. 用java举例,ja ...

  4. CentOS下j2ee环境搭建

    转自:http://www.cnblogs.com/xiaoluo501395377/archive/2013/04/01/2994485.html 因为是做j2ee后台开发的,所以在Linux上搭建 ...

  5. 如何扩大重做日志(redolog)文件的大小

    假设现有三个日志组,每个组内有一个成员,每个成员的大小为1MB,现在想把此三个日志组的成员大小都改为10MB 1.创建2个新的日志组alter database add logfile group 4 ...

  6. 初探asciinema

    在学习c++的时候想到so库注入到某些程序中,网上查了一些资料,发现了一些比较好玩的程序,分享一下. 主要可以对操作的进行视频回放,类似堡垒机的功能 安装: pip3 install asciinem ...

  7. 从windows拷贝到linux的脚本报错:未找到命令 or 语法错误

    可能真的是命令拼错了或者参数有误,也可能是语法错误. 但是但是但是,如果之前脚本运行的好好的,没做任何改动或者仅仅改了一丁点儿. 那么脚本可能在格式上存在问题,解决方案: 安装dos2unix sud ...

  8. 转:Ubuntu下下载工具安装--uget+aria2

    原文地址:http://burner1024.blog.163.com/blog/static/17447800420126191858424/ Windows下的下载工具--迅雷,之所以下载速度快, ...

  9. Django学习---Web框架及基础知识

    Django学习---Web框架 web框架的本质 我们在学socket,我们创建一个socketserver,然后运行起来,有一个client客户端要连接socket服务端,连接上之后,如果两边都没 ...

  10. 基于Tesseract的身份证识别Android端应用

    以开源的Tesseract为基础,做了一个身份证识别的app. 图片资源是百度找的,而且手机对着电脑屏幕拍照,拍出很多花纹,影响比较大,所以误差不小,实测对着自己身份证拍照会好很多. 效果图: 1.拍 ...