来自: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. 工作JS总结

    获取 <inpout type="checkbox" value="1" /> 多选项的value /*获取checkbox的全部选中项 使用方法: ...

  2. Java 中的this关键字

    1.this关键字代表当前对象 this.属性 操作当前对象的属性 this.方法 调用当前对象的方法 2.封装对象的属性的时候,经常会使用this关键字 public class Telphone ...

  3. SQL Server 2005/2008压缩数据库日志的方法

    适用于SQL Server 2005的方法 Backup Log DNName WITH no_log GO DUMP TRANSACTION DNName WITH no_log GO USE DN ...

  4. 《Java核心技术》 -- 读书笔记 ① - 预热

    引言 之前通过网上的实例自己使用了Java的一些技术及轮子快速的的“烂“造了一些小应用,但是毕竟没有认真地了解和认知Java,遂打算花一个月左右的时间来细细品味一下... 从头开始,慢慢深入!! Ja ...

  5. sublime text安装插件

    http://www.sublimetext.com/ 安装Sublime Text 2插件的方法: 1.直接安装 安装Sublime text 2插件很方便,可以直接下载安装包解压缩到Package ...

  6. How To Move a MySQL Data Directory to a New Location on Ubuntu 16.04

    16 How To Move a MySQL Data Directory to a New Location on Ubuntu 16.04 PostedJuly 21, 2016 62.1kvie ...

  7. 如何做一款自己的Android App

    转自:http://www.cnblogs.com/hubcarl/p/4030884.html 正在做App,后续补充: 概述:以我开发的一款编程学习的App[编程在线]为例讲讲如何做一款自己的An ...

  8. php redis pub/sub(Publish/Subscribe,发布/订阅的信息系统)之基本使用

    一.场景介绍 最近的一个项目需要用到发布/订阅的信息系统,以做到最新实时消息的通知.经查找后发现了redis pub/sub(发布/订阅的信息系统)可以满足我的开发需求,而且学习成本和使用成本也比较低 ...

  9. shell基本语法和执行

    执行脚本: 编写一个简单的脚本test.sh: #! /bin/sh cd .. ls Shell脚本中用#表示注释,相当于C语言的//注释.但如果#位于第一行开头,并且是#!(称为Shebang)则 ...

  10. IIS Manager 配置文件修该,允许跨域CORS访问

    IIS Manager 配置文件修该,允许跨域CORS访问 IIS Manager 的api访问会出现跨域问题,需要 IIS Manager的配置文件中修改. 配置文件的路径:C:\Program F ...