2019년 7월 31일 LCA (공통 조상)

  LCA (공통 조상)

    LCA, 최소 공통 Ancetors, 그리고 가장 최근의 공통 조상.

위키 백과의 정의를 바이 : " 트리 T U의 루트의 두 노드를 들어, V는, 최근의 공통 조상이   나타내는 노드 X, X, V의 U를 충족하는 것입니다 조상 . 깊이와 가능한 한 많은 X "

  LCA는 무엇입니까?

     친구들을 위해, 프리젠 테이션의 바이두 백과 사전 스타일은 매우 친절하지, 우리는 이미지가 실제로 LCA 무엇인지 설명하기 위해 여기에 있습니다.

     이 나무이다 (잔류 내 손을 용서)

                                       

      U 들어 : 접합 6, V : 노드 (11), 정의에 의해, 우리는 쉽게 V 조상 (부모), U를 찾을 수 있습니다. (얕은 깊이에 주문)

       U (노드 6) : 3 노드 (깊이 3), 노드 2 (깊이 2) 노드 (1) (깊이 1).

       V (11 점) 9 접합부 (4 깊이), 노드 7 (깊이 3), 노드 2 (깊이 2) 노드 (1) (깊이 1).

     LCA 정의에 기초하여 " 공개 공통 조상"단어, U는 V 노드 2 노드 (1)이다. 가장 깊은 노드 (2)의 깊이는 U (노드 6)의 LCA V (노드 11) 인 방법.

     지도보기에서 U에서 / 경로에있는 모든 점에 V 접합은 두 경로는 U 첫 번째 LCA 루트 조상 U / V 노드, 노드 교환되어 있고 V. .

  어떻게 두 점 LCA 사이 얻으려면?

     네 개의 주요 알고리즘이 있습니다 :

  • 곱하기

  • Tarjan

  • RMQ (ST + 오일러 시퀀스 테이블)

  • 트리 체인 분할

     Tarjan 이해하기 어렵지만 흔하지 RMQ 간단하고 기본적인 이해를 용이하게 상기 승산 방법은 트리 안 저부 (나중에 업데이트하지 당연하지 비둘기 ).

     오늘 우리는 LCA를 찾는 RMQ (ST 테이블 + 오일러 순서)를 연구한다.

  RMQ (ST + 오일러 시퀀스 테이블) 요청 LCA

     사전 지식 :

  • ST 表 (DP)

  • DFS

  • 전 체인 스타합니다

  요약 :이 방법은, 두 지점 사이의 오일러 주어진 트리 주어진 쿼리 RMQ 간격에 의해 결정된 테이블 ST 시퀀스로 구성되고, 두 지점이 특정 LCA 결정.

  오일러 순서

  정의 : 오일러 트리 순서는 DFS의 시퀀스 나무입니다. (1)에서 각 노드에서의 시퀀스에 추가되는 두 가지 형태가있다. 그들이 도달하면 2, 각 노드는 그 순서를 추가했다.

  나무 및 기타 문제에 대한 최초의 합계 오일러 순서, 우리는 말을하지 않는 경우. 후자는 두 LCA 문제를 찾는 데 사용됩니다.

  또는이 나무는 (내 손 잔류를 용서) :

                                               

     이 나무는 나무 오일러 순서를 얻을 수 있습니다, DFS이었다.

                 

                          실제 트리 경로 액세스 경로

  欧拉序列: 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

  (의한 DFS가 시작되는 다른 방향으로, 오일러 전체 시퀀스 순서가 역전 될 수 있음을 유의한다)

  자연 : 우리가, 오일러 순서 u는 다른 점으로 V 폴리스 트리에서 모든 하위 트리 노드 트리를 통과해야 할 첫 번째 시간을 찾을 수는 통과를 역 추적, U 노드에 다시 처음으로 나타나고, U-> V 트리 경로.

  이 두 점을 들어 LCA는 어려운 나무의 경로에 V> U-위치한 일부 LCA를 발견하고, 나무는 LCA 얕은 경로 노드해야합니다. 이것은 오일러의 서열에 도입 될 수 트리가 포인트 V에 U를 가리 입자 및

  U 및 V는 제 오일러 시퀀스의 최초 발생에 존재하는 오일러의 얕은 점의 위치 사이에 형성된 포지션 LCA 부 서열이다.

  (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.

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

 

  

 

   

  

  

  

   

추천

출처www.cnblogs.com/randomaddress/p/11273861.html