最近公共祖先 LCA Tarjan算法

来自: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 * 2]; // 树中的边

NODE *Link2[NN];
NODE edg2[NN * 2]; // 询问的点对

int idx1, idx2, N, M;
int res[MM][3]; // 记录结果,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] = 1;
    fat[u] = u;

    for (NODE *p = Link2[u]; p; p = p->nxt){
        if(vis[p->v]){
            res[p->d][2] = 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 = 0;
        memset(Link1, 0, sizeof(Link1));
        for (i = 1; i < N; i++){
            scanf("%d%d%d", &u, &v, &d);
            Add(u, v, d, edg1, Link1, idx1);
        }

        idx2 = 0;
        memset(Link2, 0, sizeof(Link2));
        for (i = 1; i <= M; i++){
            scanf("%d%d", &u, &v);
            Add(u, v, i, edg2, Link2, idx2);
            res[i][0] = u;
            res[i][1] = v;
        }

        memset(vis, 0, sizeof(vis));
        dis[1] = 0;
        Tarjan(1);

        for (i = 1; i <= M; i++){
            printf("%d\n", dis[res[i][0]] + dis[res[i][1]] - 2 * dis[res[i][2]]);
        }
    }
    return 0;
}
时间: 2024-10-03 13:45:12

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

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

最近公共祖先LCA(Tarjan算法)的思考和算法实现 LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了flase...看的时候注意一下! //还有...这篇字比较多 比较杂....毕竟是第一次嘛 将就将就 后面会重新改!!! 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先

我对最近公共祖先LCA(Tarjan算法)的理解

LCA 最近公共祖先 Tarjan(离线)算法的基本思路及我个人理解 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵树上距离最近的公共祖先节点. 所以LCA主要是用来处理当两个点仅有唯一一条确定的最短路径时的路径. 有人可能会问:那他本身或者其父亲节点是否可以作为祖先节点呢? 答案是肯定的,很简单,按照人的亲戚观念来说,你的父亲也是你的祖先,而

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

题意要求一棵树上,两个点的最近公共祖先 即LCA 现学了一下LCA-Tarjan算法,还挺好理解的,这是个离线的算法,先把询问存贮起来,在一遍dfs过程中,找到了对应的询问点,即可输出 原理用了并查集和dfs染色,先dfs到底层开始往上回溯,边并查集合并 一边染色,这样只要询问的两个点均被染色了,就可以输出当前并查集的最高父亲一定是LCA,因为我是从底层层层往上DSU和染色的,要么没被染色,被染色之后,肯定就是当前节点是最近的 #include <iostream> #include <

【算法】树上公共祖先的Tarjan算法

最近公共祖先问题 树上两点的最近公共祖先问题(LCA - Least Common Ancestors) 对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u和v的祖先且x的深度尽可能大.在这里,一个节点也可以是它自己的祖先. 例如,如图,在以A为根的树上 节点 8 和 9 的LCA为 4 节点 8 和 5 的LCA为 2 节点 8 和 3 的LCA为 1 在线算法与离线算法 区别就在于是同时处理询问后输出还是边询问边输出 在线算法是读入一组询问,查询一次后紧

hicocoder1067 最近公共祖先&#183;二(tarjan算法)

tarjan算法是处理最近公共祖先问题的一种离线算法. 算法思路: 先将所有的询问搜集起来. 然后对树进行dfs,在dfs的过程中对节点进行着色.当到达某个节点x的时候,给x着色为灰色,离开x的时候,着色为黑色. 当到达x并将其着色为灰色后,处理与x相关联的所有询问: (这里有一个显然的事实:所有的灰色节点都是x的祖先) (1)若询问的另一个节点y是灰色,那么该询问的结果为y: (2)若询问的另一个节点y是黑色,那么询问结果应为离y最近的y的灰色祖先(因为所有灰色节点都是x的祖先,所以离y最近的

最近公共祖先 LCA 倍增算法

倍增算法可以在线求树上两个点的LCA,时间复杂度为nlogn 预处理:通过dfs遍历,记录每个节点到根节点的距离dist[u],深度d[u] init()求出树上每个节点u的2^i祖先p[u][i] 求最近公共祖先,根据两个节点的的深度,如不同,向上调整深度大的节点,使得两个节点在同一层上,如果正好是祖先结束,否则,将连个节点同时上移,查询最近公共祖先. void dfs(int u){ for(int i=head[u];i!=-1;i=edge[i].next){ int to=edge[i

POJ 1330 最近公共祖先LCA(Tarjan离线做法)

题目链接:http://poj.org/problem?id=1330 题目大意十分明了简单,就是给定一棵树,求某两个子节点的最近公共祖先,如果尚不清楚LCA的同学,可以左转百度等进行学习. 稍微需要注意的是,建树顺序需要按照题目给定的顺序进行,也就是说根被设定成第一个给出的结点,如样例2. 此题网上题解颇多,但是多是使用的邻接表存图,于是我这里采用了边表,不过实质上Tarjan的部分思想都是一样的,均利用了并查集. AC代码: #include <cstdio> #include <c

POJ 1470 Closest Common Ancestors【最近公共祖先LCA】

题目链接:http://poj.org/problem?id=1470 题目大意:给出一棵树,再给出若干组数(a,b),输出节点a和节点b的最近公共祖先(LCA) 就是很裸的LCA,但是我用的是<挑战程序设计竞赛>上的"基于二分搜索的算法求LCA",我看网上用的都是tarjan算法.但是我的代码不知道为什么提交上去 wrong answer,自己想的很多测试数据也都和题解结果一样,不知道错在哪里,所以把代码保存一下,留待以后解决...... 如果读者有什么建议,希望提出来,

Tarjan 算法求 LCA / Tarjan 算法求强连通分量

[时光蒸汽喵带你做专题]最近公共祖先 LCA (Lowest Common Ancestors)_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili tarjan LCA - YouTube Tarjan算法_LCA - A_Bo的博客 - CSDN博客 Tarjan离线算法求最近公共祖先(LCA) - 初学者 - CSDN博客 最近公共祖先(LCA) - riteme.site Fuzhou University OnlineJudge P3379 [模板]最近公共祖先(LCA) - 洛谷 |