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

Posted tech-chen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 }

 

 

以上是关于LCA(最近公共祖先)--tarjan离线算法 hdu 2586的主要内容,如果未能解决你的问题,请参考以下文章

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

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

tarjan算法求最近公共祖先

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

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

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