Q:为什么我在有些地方看到的是最小公共祖先?

A:最小公共祖先是LCA(Least Common Ancestor)的英文直译,最小公共祖先与最近公共祖先只是叫法不同。

Q:什么是最近公共祖先(LCA)?

A:最近公共祖先的概念是很好理解的。首先,你需要脑补出一棵树(它可以是二叉树,也可以是多叉树。)之后,请你再在你脑补出的树上任取两个点。每个点都可以到达树根,且到达的路径是唯一的,既然两个点都可以到达树根,那么根无疑是这两个点的公共祖先。然而,根却不一定是这两个点的最近公共祖先,相反,离根距离最远且在两条点到根路径上的点才是最近公共祖先(最近公共父节点)。

实现求LCA的方法有很多种,无论是离线还是在线,是TARJAN还是RMQ等等都可以实现。在此,安利一种PO主钟爱的方法:倍增,来实现LCA。

首先,你不可以认为倍增实现LCA和RMQ实现LCA是指的同一回事情,哪怕RMQ的完成是用了倍增思想。事实上,RMQ实现LCA的程序比较繁琐,并且需要你考虑到众多细节。而这些细节的调试在比赛有限的时间内无疑是要爆炸的。比如说,PO主就在某次D2T3跪在了RMQ实现LCA上QWQ。与RMQ相反,倍增实现的代码要比RMQ实现简单一些,并且好脑补,而且容易调试,相信大家一定可以弄明白倍增实现LCA的QWQ

在没有学习倍增写LCA之前,你是怎么样求LCA的呢?至少,我是老老实实地让这两个点一步一步往上移并找出它们的路径第一次交汇的地方。这种方法固然可行、好想,但它的效率实在不高。但是,我们完全可以通过提高“这两个点一步一步往上移”来提高效率。

所以,我们采用倍增的思路来预处理,分别记录这点的祖先,记录为anc[i][j]。即为第i个点往上2^j个祖先。比如说,当j=0时,2^j=1,anc[i][j]是第i个点的上一个节点,即它的父亲节点。

那么该如何预处理出anc数组呢?

  1. int anc[][];
  2. int fa[];
  3. vector <int > tree[];
  4. int deep[];
  5.  
  6. void dfs(int x)
  7. {
  8. anc[x][]=fa[x];
  9. for (int i=;i<=;i++)
  10. {
  11. anc[x][i]=anc[anc[x][i-]][i-];//倍增思想的体现。不妨在纸上试着画一棵树,脑补一下QWQ
  12. }
  13.  
  14. for (int i=;i<tree[x].size();i++)
  15. {
  16. if (tree[x][i]!=fa[x])
  17. {
  18. int y=tree[x][i];
  19. fa[y]=x;//记录父亲节点
  20. deep[y]=deep[x]+;//记录深度
  21. dfs(y);
  22. }
  23. }
  24. }

通过从根节点开始的DFS,我们就预处理好了ANC数组。

下面,我们来考虑如何处理LCA查询。即每次给你两点X和Y,求出它们的LCA(X,Y)。在有了ANC数组之后,求出最近公共祖先就会变得很简单。

首先,让X,Y在同一深度上。在大多数情况下,查询给你的两个点X和Y它们的深度是不同的。但是,如果两点的深度相同,我们就可以实现两个点同时倍增比较何时祖先相同。所以,第一步是使X,Y中深度较深的点往上移动直到与另一个点深度相同。当然,点的移动也可以用倍增完成。

然后,当两点深度相同后,同时向上倍增两个点,当它们祖先刚好相同时,这个祖先就是它们的LCA。

如果你还是有一些不理解的话,不妨看LCA实现的代码QAQ

  1. int lca(int x,int y)
  2. {
  3. if (deep[x]<deep[y]) _swap(x,y);//我们希望X是较深的点。
  4.  
  5. for (int i=;i>=;i--)//这个循环在完成第一步。
  6. {
  7. if (deep[y]<=deep[anc[x][i]]) //不可以丢掉“=“哦Q^Q
  8. {
  9. x=anc[x][i];
  10. }
  11. }
  12.  
  13. if (x==y) return x;//如果Y是X的祖先,就可以直接返回结果了。
  14.  
  15. for (int i=;i>=;i--)
  16. {
  17. if (anc[x][i]!=anc[y][i]) //第二步。
  18. {
  19. x=anc[x][i];
  20. y=anc[y][i];
  21. }
  22. }
  23.  
  24. return anc[x][];//注意第二步IF语句的条件。
  25. }

Q为什么可以保证倍增就可以刚好到达那个我想要的点呢?

A:你不妨假设你现在点的位置到你想要的点的位置之间距离为D,那么D是整数,那么D一定可以用二进制表示,还记得ANC的第二维代表什么意思吗?二进制数D为1的地方就是我们需要往上翻的地方(比较抽象,适合画一画QAQ),为0的地方我们不动就好。

同时,LCA还可以用于求树上两点之间的距离。比如说POJ1986.

Distance Queries
Farmer John's cows refused to run in his marathon since he chose a path much too long for their leisurely lifestyle. He therefore wants to find a path of a more reasonable length. The input to this problem consists of the same input as in "Navigation Nightmare",followed by a line containing a single integer K, followed by K "distance queries". Each distance query is a line of input containing two integers, giving the numbers of two farms between which FJ is interested in computing distance (measured in the length of the roads along the path between the two farms). Please answer FJ's distance queries as quickly as possible!
 

Input

* Lines 1..1+M: Same format as "Navigation Nightmare"

* Line 2+M: A single integer, K. 1 <= K <= 10,000

* Lines 3+M..2+M+K: Each line corresponds to a distance query and contains the indices of two farms.

Output

* Lines 1..K: For each distance query, output on a single line an integer giving the appropriate distance.

Sample Input

  1. 7 6
  2. 1 6 13 E
  3. 6 3 9 E
  4. 3 5 7 S
  5. 4 1 3 N
  6. 2 4 20 W
  7. 4 7 2 S
  8. 3
  9. 1 6
  10. 1 4
  11. 2 6

Sample Output

  1. 13
  2. 3
  3. 36

Hint

Farms 2 and 6 are 20+3+13=36 apart

这道题目可以直接忽视方向哦QAQ

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <algorithm>
  4. #include <cmath>
  5. #include <set>
  6. #include <vector>
  7. #include <cstring>
  8. using namespace std;
  9.  
  10. int n,m,q;
  11. int up[],de[],dp[][],fa[];
  12. int pn[],pg[],pv[],st[];
  13. int tot=;
  14.  
  15. void init()
  16. {
  17. memset(up,,sizeof(up));
  18. memset(de,,sizeof(de));
  19. memset(dp,,sizeof(dp));
  20. memset(pn,,sizeof(pn));
  21. memset(pg,,sizeof(pg));
  22. memset(pv,,sizeof(pv));
  23. memset(fa,,sizeof(fa));
  24.  
  25. return ;
  26. }
  27.  
  28. void ins(int x,int y,int w)
  29. {
  30. pv[++tot]=y;
  31. pg[tot]=w;
  32. pn[tot]=st[x];
  33. st[x]=tot;
  34.  
  35. return;
  36. }
  37.  
  38. void dfs(int x)
  39. {
  40. dp[x][]=fa[x];
  41. for(int i=;i<;i++)
  42. {
  43. dp[x][i]=dp[dp[x][i-]][i-];
  44. }
  45.  
  46. for (int i=st[x];i;i=pn[i])
  47. {
  48. int cur=pv[i];
  49. if (cur==fa[x]) continue;
  50. de[cur]=de[x]+;
  51. up[cur]=up[x]+pg[i];
  52. fa[cur]=x;
  53. dfs(cur);
  54. }
  55.  
  56. return;
  57. }
  58.  
  59. int lca(int x,int y)
  60. {
  61. if (de[x]<de[y]) {
  62. int t=x;
  63. x=y;
  64. y=t;
  65. }
  66.  
  67. for (int i=;i>=;i--)
  68. {
  69. if (de[dp[x][i]]>=de[y]) x=dp[x][i];
  70. }
  71.  
  72. if (x==y) return x;
  73.  
  74. for (int i=;i>=;i--)
  75. {
  76. if (dp[x][i]!=dp[y][i])
  77. {
  78. x=dp[x][i];
  79. y=dp[y][i];
  80. }
  81. }
  82.  
  83. return dp[x][];
  84. }
  85.  
  86. int main()
  87. {
  88.  
  89. while(~scanf("%d%d",&n,&m))
  90. {
  91. init();
  92. int a,b,c;
  93. for (int i=;i<=m;i++)
  94. {
  95. char s[];
  96. scanf("%d%d%d%s",&a,&b,&c,s);
  97. ins(a,b,c); ins(b,a,c);
  98. }
  99.  
  100. fa[]=;
  101. de[]=up[]=;
  102. dfs();
  103.  
  104. scanf("%d",&q);
  105.  
  106. while(q--)
  107. {
  108. scanf("%d%d",&a,&b);
  109.  
  110. printf("%d\n",up[a]+up[b]-*up[lca(a,b)]);//求树上距离。
  111. }
  112. }
  113.  
  114. return ;
  115. }

最近公共祖先:LCA及其用倍增实现 +POJ1986的更多相关文章

  1. 【lhyaaa】最近公共祖先LCA——倍增!!!

    高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...

  2. Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)

    Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...

  3. POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)

    POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...

  4. POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)

    POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...

  5. [模板] 最近公共祖先/lca

    简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...

  6. 最近公共祖先 LCA 倍增算法

          树上倍增求LCA LCA指的是最近公共祖先(Least Common Ancestors),如下图所示: 4和5的LCA就是2 那怎么求呢?最粗暴的方法就是先dfs一次,处理出每个点的深度 ...

  7. luogu3379 【模板】最近公共祖先(LCA) 倍增法

    题目大意:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 整体步骤:1.使两个点深度相同:2.使两个点相同. 这两个步骤都可用倍增法进行优化.定义每个节点的Elder[i]为该节点的2^k( ...

  8. 最近公共祖先 LCA 倍增法

    [简介] 解决LCA问题的倍增法是一种基于倍增思想的在线算法. [原理] 原理和同样是使用倍增思想的RMQ-ST 算法类似,比较简单,想清楚后很容易实现. 对于每个节点u , ancestors[u] ...

  9. lca最近公共祖先(st表/倍增)

    大体思路 1.求出每个元素在树中的深度 2.用st表预处理的方法处理出f[i][j],f[i][j]表示元素i上方第2^j行对应的祖先是谁 3.将较深的点向上挪,直到两结点的深度相同 4.深度相同后, ...

随机推荐

  1. Python自动化运维之2、运算符与数据类型

    python对象的相关术语: python程序中保存的所有数据都是围绕对象这个概念展开的: 程序中存储的所有数据都是对象 每个对象都有一个身份.一个类型和一个值 例如,school='MaGe Lin ...

  2. 如何使用sublime编辑器运行python程序

    现在越发喜欢sublime编辑器了,不仅界面友好美观.文艺,可扩展性还特别强. sublime本身是不具备运行python程序的能力的,需要做些设置才可以.以下是安装好sublime后设置的步骤: 点 ...

  3. 再次探讨C++的动态绑定和静态绑定

    以前在学习C++的时候,对动态绑定和静态绑定的理解是:静态绑定是编译时决定的,非虚函数基本都是静态绑定:而动态绑定用于虚函数,是为了实现多态.这样理解没什么大的问题,但我一直疑惑的是,既然静态绑定可以 ...

  4. 一条带分页的sql

    SELECT * FROM (SELECT USERID, TYPE, TYPE_DESC, SEX, USERNAME, HEADPORTRAIT, HOSPITAL, HLEVEL, DEPT, ...

  5. Java DES 测试

    package com.des.test; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyEx ...

  6. Android上传文件之FTP

    android客户端实现FTP文件(包括图片)上传应该没什么难度.写下来就了为了记录一下,望能帮到新手. 需要用到 commons-net-3.0.1.jar,后面附上jar包. 直接上代码: /** ...

  7. linux教程之四

    相信不少想学习linux的新手们正愁不知道看什么linux学习教程好,下面小编给大家收集和整理了几点比较重要的教程,供大家学习,如需想学习更多的话,可到wdlinux学堂寻找更多教程.   linux ...

  8. FILTER 执行次数

    select count(*) from SAVJ_ATOMJOURBAK where ((list_flag='1' and prt_flag='0') and acct_no not in (se ...

  9. Linux 常用命令记录

    1.查看磁盘空间使用情况 df -[a i m] 或更多 df -lh 2.查看目录文件占用大小 du -sh * du --max-depth=1 -lh 3.内存使用qingkuang free ...

  10. MsgBox-官方文档

    http://s3.envato.com/files/293712/index.html