2019/7/31 LCA (common ancestor)

  LCA (common ancestor)

    LCA, Lowest Common Ancetors, and most recent common ancestor.

Baidu Wikipedia definition: " For two nodes of the root of the tree T u, v, recent common ancestor   represents a node x, x is to meet u, v's ancestor depth and x as large as possible. "

  What is LCA?

     For some friends, Baidu Encyclopedia style of presentation is not very friendly, and we are here to explain what the image actually is LCA.

     This is a tree (forgive my hand residual)

                                       

      For u: junction 6, v: node 11, by definition, we can easily find u, v their ancestors (parent). (Order from deep to shallow)

       u (node ​​6): node 3 (depth 3), node 2 (Depth 2), node 1 (depth 1).

       V (point 11): junction 9 (4 depth), the node 7 (depth 3), node 2 (Depth 2), node 1 (depth 1).

     LCA based on the definition " public common ancestor" word, u, v is the node 2, node 1. And wherein the depth of the deepest node 2 is the u (node 6) LCA v (node 11).

     From the map view, from u / v junction to all points on the path are the root ancestor u / v nodes, the nodes interchange the two paths is the first LCA u and v. .

  How to get between any two points LCA?

     There are four major algorithms:

  • Multiply

  • Tarjan

  • RMQ (ST + Euler sequence table)

  • Tree chain split

     Multiplication method wherein more basic, simple, Tarjan difficult to understand, but less common RMQ readily appreciated, the tree does not lower section (later updated absolutely not pigeons ).

     Today we study RMQ (ST table + Euler sequence) seeking LCA.

  RMQ (ST + Euler sequence table) request LCA

     Pre-knowledge:

  • ST表(DP)

  • DFS

  • Former chain to star

  Synopsis: This method is configured by the table ST sequence determined by Eulerian given tree given query RMQ interval between two points, two points determined given LCA.

  Euler sequence

  Definition: Euler tree sequence is a sequence tree of DFS. There are two forms: 1, in and out of each node are added to the sequence. 2, once they reach each node took him add sequence.

  The first summation Euler sequence for the tree and other issues, if we do not speak. The latter is used to find the two LCA problems.

  Or is this tree (forgive my hand residues) :

                                               

     These trees were DFS, you can get to the tree Euler sequence.

                 

                          The actual tree path access path

  欧拉序列:1->2->7->8->7->10->13->10->12->10->7->9->11->9->7->2->3->6->3->5->3->4->3->2->1

  (It is noted that due to the different directions DFS begins, Euler whole sequence order may be reversed)

  Nature : we can find in the Euler sequence, it is the first time u need to traverse from tree to u all sub-tree node tree to another point v appears for the first time, and back to u node, backtracking traversal u-> v tree path.

  For these two points LCA, difficult to find some LCA located u-> v on the path of the tree, and the tree must be LCA shallowest path node. It can be introduced in a sequence of Euler particles tree, point to point u to v, and

  U and v are positions LCA section formed between the position of the shallowest point of the Euler sequences present in the first occurrence of the first Euler sequence.

  (eg.对于上图树中结点6与结点11,其在欧拉序列中形成区间为11->9->7->2->3->6,深度分别为5->4->3->2->3->4,LCA即为最浅点结点2(深度2)。)

  P.S.  实际实现时注意两节点第一次出现位置的大小,可能需要交换顺序。 

  2.ST表

  原理我们暂且不讲,不了解的同学可以先将它理解为一种快速查找给定区间最大/最小值(区间RMQ问题)的算法。

  在求LCA的过程中,我们所需的ST表与普通ST表略微不同。因为我们在查找最小值时还需要查找此最小值对应的节点编号,以此直接求出LCA。

  此问题的解决方法也比较简单,设定一个rec[ ]数组,使其在st表结构数组st[ ]更新时同步更新,查找时比较数组st[ ]得到某一结点深度最浅并返回此结点对应数组

  rec[ ]中的节点编号。

  至此,我们对此算法原理研究结束。

  3.代码实现

  上代码!

  声明部分:

 1 #include <iostream>
 2 #include <cstdio>//标准输入输出
 3 #include <cmath>//用于ST表中求解log
 4 using namespace std;
 5 int n,m,s,cnt,tot;// s:根节点,cnt:链式前向星,tot:总欧拉序列长度
 6 int head[1000005];//链式前向星不解释
 7 int depth[1000005];//记录当前结点深度
 8 int num[1000005];//记录节点第一次出现位置
 9 int rec[2000005][20];//查询数组
10 int st[2000005][20];//ST表结构数组
11 int euler[1000005];//欧拉序列数组
12 //int dp[1000005];求节点深度数组 
13 //int wd[1000005];求某一深度树的宽度数组

 

  链式前向星存边:

 1 struct edge
 2 {
 3     int nxt;
 4     int to;
 5     //int dis;边权值//在本示例中默认边权为1
 6 }e[4000005];//建议开4倍数组
 7 void add(int x,int y/*,int d*/)
 8 {
 9     e[++cnt].nxt=head[x];
10     //e[cnt].dis=d;
11     e[cnt].to=y;
12     head[x]=cnt;
13 }    

  DFS:

 1 void dfs(int x,int dep)//x为当前节点,dep为当前节点深度
 2 {
 3 
 4     num[x]=++tot;//记录x节点第一次出现位置
 5     depth[tot]=dep;//对应深度
 6     euler[tot]=x;//记录序列
 7     //dp[x]=max(dp[x],depth[tot]); //求某一结点深度
 8     //cout<<"#访问节点:"<<x<<"   depth数组:"<<depth[tot]<<endl;
 9     for(int i=head[x];i;i=e[i].nxt)//遍历边
10     {
11         int p=e[i].to;
12         if(num[p]==0)//p节点如未出现
13         {
14             dfs(p,dep+1);//遍历
15             euler[++tot]=x;//回溯后记录序列
16             depth[tot]=dep;//记录对应深度
17         }
18     }
19     return ;
20 }

  ST表求RMQ:

 1 void RMQ(int N)//N:欧拉序列长度
 2 {
 3     for(int j=1;j<=(int)(log((double)N)/log(2.0));j++)
 4     {
 5         for(int i=1;i<=N;i++)
 6         {
 7             if(i+(1<<j)-1<=N)
 8             if(st[i][j-1]<st[i+(1<<(j-1))][j-1])//同步更新rec[ ]数组
 9                 st[i][j]=st[i][j-1],rec[i][j]=rec[i][j-1];
10             else 
11                 st[i][j]=st[i+(1<<(j-1))][j-1],rec[i][j]=rec[i+(1<<(j-1))][j-1];
12         }
13     }
14 }
15 
16 int search(int l,int r)
17 {
18     int k=(int)(log((double)(r-l+1))/log(2.0));
19     if(st[l][k]<st[r-(1<<k)+1][k])//比较后返回rec[ ]数组对应节点编号
20     return rec[l][k];
21     else
22     return rec[r-(1<<k)+1][k];
23 }

  主函数:

 1 int main()
 2 {
 3     cin>>n>>m>>s;
 4     for(int i=1;i<=n-1;i++)//读边
 5     {
 6         int a,b;
 7         scanf("%d %d",&a,&b);
 8         add(a,b);//无向图正反存边
 9         add(b,a);
10     }
11     dfs(s,1);//开始遍历
12     for(int i=1;i<=tot;i++)//初始化
13     {
14         st[i][0]=depth[i],rec[i][0]=euler[i];
15     }
16     RMQ(tot);//构建ST表
17     /*//接下来是不必要部分//当初死在了这里
18     int dcnt=0,maxx=0;//dcnt:数的最大深度,maxx:数的最大宽度
19     for(int i=1;i<=n;i++)
20     {
21         wd[dp[i]]++;//统计所有深度为dp[i]的节点求出当前深度树宽度
22     }
23     for(int i=1;i<=n;i++)
24     {
25         if(wd[i]==0)
26         {
27             break;
28         }
29         dcnt++;//统计最大深度//笨方法
30     }
31     for(int i=1;i<=wcnt+1;i++)
32     {
33         maxx=max(maxx,wd[i]);//求最大宽度
34     }
35 36     */
37     for(int i=1;i<=m;i++)//查询部分
38     {
39         int l,r,fg=0;//l:节点u,r:节点v,fg:交换标志
40         scanf("%d %d",&l,&r);
41         if(num[l]>num[r])//交换
42         {
43             swap(num[l],num[r]);
44             fg=1;//交换后标记
45         }
46         printf("%d\n",search(num[l],num[r]));//查询并输出
47         if(fg==1)//交换回来!!!!!记得交换回来!!!!!//p.s.2019/7/29模拟赛爆0 r.i.p
48         swap(num[l],num[r]);
49     }
50     return 0;
51 }

  结语

  LCA问题较为常见,应至少掌握一种方法。

  拓展:

  此种思想也可用于求树(最大)宽度/深度,树上距离,三点LCA等问题。

  相关题目

  洛谷 P3379 【模板】最近公共祖先(LCA)

  洛谷 P3884  [JLOI2009]二叉树问题  

  洛谷 P4281 [AHOI2008]紧急集合 / 聚会(三点LCA)

  //暂时只想到这么多·········

  Q.E.D.(大雾)

  p.s.

  由于是萌新第一次撰写题解,在语言及思路等方面定会有不足之处,请大家多多包涵,也欢迎各位大佬指正。

 

  

 

   

  

  

  

   

Guess you like

Origin www.cnblogs.com/randomaddress/p/11273861.html