LCA(最近公共祖先)--tarjan离线算法 hdu 2586

HDU 2586 How far away ?

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 11320    Accepted Submission(s):
4119

Problem Description

There are n houses in the village and some
bidirectional roads connecting them. Every day peole always like to ask like
this "How far is it if I want to go from house A to house B"? Usually it hard to
answer. But luckily int this village the answer is always unique, since the
roads are built in the way that there is a unique simple path("simple" means you
can‘t visit a place twice) between every two houses. Yout task is to answer all
these curious people.

Input

First line is a single integer T(T<=10), indicating
the number of test cases.
  For each test case,in the first line there are
two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses
and the number of queries. The following n-1 lines each consisting three numbers
i,j,k, separated bu a single space, meaning that there is a road connecting
house i and house j,with length k(0<k<=40000).The houses are labeled from
1 to n.
  Next m lines each has distinct integers i and j, you areato answer
the distance between house i and house j.

Output

For each test case,output m lines. Each line represents
the answer of the query. Output a bland line after each test case.

Sample Input

2
3 2
1 2 10
3 1 15
1 2
2 3

2 2
1 2 100
1 2
2 1

Sample Output

10
25
100
100

Source

ECJTU
2009 Spring Contest

解析:tarjan离线算法求LCA:

概念描述:

LCA(Least Common Ancestors):即最近公共祖先,是指这样一个问题:在有根树中,找出某两个结点u和v的所有祖先中距离(u,v)最近的那个公共祖先(也就是离根最远的那个公共祖先)。

时间和空间复杂度:

  • 时间复杂度:当询问次数为Q,节点数为N时,时间复杂度为O(N+Q)。
  • 空间复杂度:①建图时存储的空间大小(树的节点个数个空间);②深搜时遍历树时需要的空间大小(树的最大深度)

算法实现基于基本原理:

算法是基于DFS并查集来实现的。

算法流程 及 对求LCA(u,v)的证明:

  1. 从根节点(root)开始深搜,
  2. 对于新搜索到的一个节点(x),首先创建由这个结点构成的集合(由par数组维护,par[x]=x,当前这个集合中只有元素x)
  3. 然后依次搜索并处理该节点包含的所有子树(搜素和处理是个递归的过程。结合第5点理解:每搜索完一棵子树,则可确定子树内的LCA询问都已解,其他的LCA询问的结果必然在这个子树之外。)
  4. 当搜索完该节点的所有子树以后,在回溯时,把当前节点的par[x]=(x的父亲节点)(集合归并)
  5. 然后开始处理原先所有询问中包含了该节点的所有询问及求LCA(u,?)(体现了离线算法,对询问次序按深搜时遍历到的节点顺序进行重组
    • 在处理包含该节点的询问中,先判断当前正在处理的这条询问(求LCA(u,v))中另一个节点(v)是否也已经被遍历过
    • 还未遍历,则暂不处理否则LCA(u,v)= FindPar(par[v])(并查集)(证明:因为v是在遍历到u(也就是当前的x节点)之前先遍历到了。如果有一个从当前结点(u)到结点v的询问,且v已被检查过,则由于进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v所在集合的祖先。)
    • 包含该节点的所有询问全部处理完毕

<具体结合遍历和并查集的归并的顺序理解,见图>

处理LCA(3,4):因为3在遍历到4之前先被访问到,所以LCA(3,4)=FindPar(par[3])=3;(此时对LCA(4,5)的询问操作暂被跳过。

处理LCA(5,4):因为4在遍历到5之被访问,所以LCA(5,4)=Find(par[4])=2

扩展:求树上两点(u,v)间的最短距离

求树上两点间u,v的最短距离的方法:记dis[u]为u到根节点的距离

那么u到v之间的距离:ans[u][v]=dis[u]+dis[v]-2*dis[lca[u][v]](减去到根节点的公共距离的两倍);

题解:

 1 #include<cstring>
 2 #include<iostream>
 3 using namespace std;
 4 #include<cstdio>
 5 #define M 201
 6 #include<vector>
 7 #define N 40100
 8 vector <int> que[N];/*储存询问队列*/
 9 int ans[M];/*存着答案*/
10 int n,m,u,v,k;
11 struct Edge{
12     int v,last,w;/*边表*/
13 }edge[N*2];
14 int head[N],dis[N]={0};
15 int T,t=0;
16 int father[N],ance[N];/*father[N]表示当前的处理的子树,ance代表这个点当前的祖先*/
17 bool visit[N],root[N];/*visit判断当前的点的子树lca是否求过,root是寻找根节点*/
18 void add_edge(int u,int v,int w)
19 {
20     ++t;
21     edge[t].v=v;
22     edge[t].w=w;
23     edge[t].last=head[u];
24     head[u]=t;
25 }
26 void input()
27 {
28     memset(visit,false,sizeof(visit));
29     memset(root,false,sizeof(root));
30     memset(head,0,sizeof(head));
31     memset(dis,0,sizeof(dis));
32     memset(edge,0,sizeof(edge));
33     scanf("%d%d",&n,&m);
34     for(int i=1;i<=n-1;++i)
35     {
36         scanf("%d%d%d",&u,&v,&k);
37         add_edge(u,v,k);
38         root[v]=true;
39         ance[i]=i;/*初始化*/
40         father[i]=i;
41     }
42     for(int i=1;i<=m;++i)
43     {
44         scanf("%d%d",&u,&v);
45         que[u].push_back(i);/*因为离线算法求出结果是无法知道他的查询顺序的,但是我们要按照查询顺序输出,所以就在查询序列的偶数位存着下一位的在ans的顺序*/
46         que[u].push_back(v);
47         que[v].push_back(i);
48         que[v].push_back(u);
49     }
50 }
51 int find(int x)
52 {
53     return (father[x]==x)?father[x]:father[x]=find(father[x]);
54 }
55 void tarjan(int k,int w)
56 {
57     ance[k]=k;
58     dis[k]=w;
59     for(int l=head[k];l;l=edge[l].last)
60     {
61         tarjan(edge[l].v,dis[k]+edge[l].w);
62         father[edge[l].v]=k;/*father存着当前点与全部的子树上的集合,同时把子树直接点的祖先设为k,所以查询一个子树上的点的区间的时候,要先用find找出代表元素,再求祖先*/
63         ance[edge[l].v]=k;
64     }
65     visit[k]=true;
66     int size=que[k].size();
67     for(int i=1;i<size;i+=2)
68     {
69         if(visit[que[k][i]])/*如果这条边的另一个点的lca已经求出来,那么这个点所在集合的祖先就是这两个点的最近公共祖先*/
70         {
71             int zu=ance[find(que[k][i])];
72             ans[que[k][i-1]]=dis[k]+dis[que[k][i]]-2*dis[zu];
73         }
74     }
75 }
76 int main()
77 {
78     scanf("%d",&T);
79     while(T--)
80     {
81         input();
82         for(int i=1;i<=n;++i)
83         {
84             if(!root[i])/*从根节点开始深搜*/
85             {
86                 tarjan(i,0);
87                 break;
88             }
89         }
90         for(int i=1;i<=m;++i)
91           printf("%d\n",ans[i]);
92     }
93     return 0;
94 }
时间: 2024-10-20 01:38:39

LCA(最近公共祖先)--tarjan离线算法 hdu 2586的相关文章

笔记:LCA最近公共祖先 Tarjan(离线)算法

LCA最近公共祖先 Tarjan他贱(离线)算法的基本思路及其算法实现 本文是网络资料整理或部分转载或部分原创,参考文章如下: https://www.cnblogs.com/JVxie/p/4854719.html http://blog.csdn.net/ywcpig/article/details/52336496 https://baike.baidu.com/item/最近公共祖先/8918834?fr=aladdin 最近公共祖先简称LCA(Lowest Common Ancesto

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

学习博客:  http://noalgo.info/476.html 讲的很清楚! 对于一颗树,dfs遍历时,先向下遍历,并且用并查集维护当前节点和父节点的集合.这样如果关于当前节点(A)的关联节点(B)(及要求的最近祖先的另一个点)之前被访问过,那么 B可定已经属于一个集合,先前对于访问过的点,已经维护了那个点所在集合的根,所以找到B节点所在集合的根,那么这个点就是最近的根,因为对于dfs访问的顺序.

最近公共祖先 tarjan离线算法 C++

最近做到一道题目,大概的意思就是求一个多叉树中两个节点的最近公共祖先,输入是用邻接矩阵表示的. 要想理解tarjan算法并实现它,需要先理解一下内容: 1) 深度优先搜索:tarjan算法核心思想:当某节点刚刚搜索完毕时,看与其相关的结点v是否已经被访问,如果v已经被访问过了,则它们的最近公共祖先就是v的祖先. 2) 并查集原理和实现方法,并查集的代表和祖先的区别(其实也可以一起表示),祖先的更新时刻 3) 如何表示多叉数(邻接链表,邻接矩阵),如何表示查询对,如何记录查询结果 下面是c++实现

POJ 1330 Nearest Common Ancestors(最近公共祖先 Tarjan离线)

题目链接:http://poj.org/problem?id=1330 题目: Description A rooted tree is a well-known data structure in computer science and engineering. An example is shown below:  In the figure, each node is labeled with an integer from {1, 2,...,16}. Node 8 is the ro

求LCA最近公共祖先的离线Tarjan算法_C++

最近一直在刷算法,过几天再来写详细的思路 先丢个模板,这个是用双链树存的 1 #include<algorithm> 2 #include<iostream> 3 #include<cstdlib> 4 #include<cstring> 5 #include<cstdio> 6 #include<cmath> 7 #include<stack> 8 #define N 100001 9 using namespace s

[笔记]LCA最近公共祖先---倍增在线算法

059M37853N虏3Jhttp://www.zcool.com.cn/collection/ZMTg2OTM5ODg=.html 痹o83RI世9EUS两http://www.zcool.com.cn/collection/ZMTg2OTQwMTY=.html 猩骋05K型51抡MChttp://www.zcool.com.cn/collection/ZMTg2OTQxMjg=.html 4辣腾膛且j匠9坝3凳W1http://www.zcool.com.cn/collection/ZMTg

LCA(最近公共祖先)——离线 Tarjan 算法

一.梳理概念 定义:对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u.v的祖先且x的深度尽可能大. 通俗地讲,最近公共祖先节点,就是两个节点在这棵树上深度最大的公共的祖先节点,即两个点在这棵树上距离最近的公共祖先节点. 提示:父亲节点也是祖先节点,节点本身也是它的祖先节点. 给出一棵树,如图所示: 由上面的定义可知:3和5的最近公共祖先为1,5和6的最近公共祖先为2,2和7的最近公共祖先为2, 6和7的最近公共祖先为4. 二.繁文缛节 注意注意注意!!!尚

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

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

HDU 2586 How Far Away?(Tarjan离线算法求lca)

题意:给定一棵树n个节点m个询问,每次询问两个节点之间的距离. 思路:Tarjan离线算法求lca. 这题一开始交了n发一直爆栈.......百度了一下大概说的是这样hdu用的是windows服务器所以栈大小极其坑爹,稍微深一点的递归就会爆栈(正式比赛一般不会爆) 解决方法就是加一句#pragma comment(linker, "/STACK:1024000000,1024000000") 用c++交就好.....当然这只是针对比较坑爹oj来说的取巧的方法 #include<c