当前位置:   article > 正文

最近公共祖先LCA的三种求法_最近公共祖先(lca)的算法:

最近公共祖先(lca)的算法:

最近公共祖先的定义:如果结点c满足:

目录

1.向上标记法

2.倍增算法

3.用离线的tarjan算法求LCA 时间复杂度O(n+m)

         1172. 祖孙询问

1171. 距离



1.c是a和b的公共祖宗结点。

2.c是距离a,b最近的公共祖宗节点。

那么就称c是a和b的最近公共祖先。

1.向上标记法

分别从a,标记所有a的祖宗节点,再让b向上跳,第一次遇到标记的结点就是最近公共祖先。

n个结点时间复杂度 O(n*n)

2.倍增算法

step1:预处理所有点从上走2的k次幂的父亲是谁,fa[i][j] 表示从i开始向上走2的j次幂能走到的节点其中

0<=j<=log2(n)

同时预处理每个节点的深度depth[i].

step2:求x,y的最近公共祖先:

1.假设x的深度比y大,也即x在y的下面,先让x倍增跳到与y同一深度(枚举2的整数次幂)

2.让两个点同时往上跳,一直跳到最近公共祖先的下一层,答案就是 fa[a][0]或者fa[b][0];

时间复杂度O(n*logn);

3.用离线的tarjan算法求LCA 时间复杂度O(n+m)

1.利用深度优先遍历将图分为三类同时用并查集得出每个已经遍历过点的代表元:

0.没有访问的点

1.正在搜索的点

2.已经遍历的点

 2.所有与正在搜索点集有关联的的已经遍历过点的最近公共祖先就是这个点的代表元。

注意点:因为是离线算法需要存储每次询问和每次询问的答案。

1172. 祖孙询问​​​​​​​

给定一棵包含 n 个节点的有根无向树,节点编号互不相同,但不一定是1∼n。

有 m 个询问,每个询问给出了一对节点的编号 x 和 y,询问 x 与 y 的祖孙关系。

输入格式

输入第一行包括一个整数 表示节点个数;

接下来 n 行每行一对整数 a 和 b,表示 a 和 b 之间有一条无向边。如果 b 是 −1,那么 a 就是树的根;

第 n+2 行是一个整数 m 表示询问个数;

接下来 m 行,每行两个不同的正整数 x 和 y,表示一个询问。

输出格式

对于每一个询问,若 x 是 y 的祖先则输出 1,若 y 是 x 的祖先则输出 2,否则输出 0。

数据范围

1≤n<=4e4,
1≤每个节点的编号≤4e4

输入样例:

  1. 10
  2. 234 -1
  3. 12 234
  4. 13 234
  5. 14 234
  6. 15 234
  7. 16 234
  8. 17 234
  9. 18 234
  10. 19 234
  11. 233 19
  12. 5
  13. 234 233
  14. 233 12
  15. 233 13
  16. 233 15
  17. 233 19

输出样例:

  1. 1
  2. 0
  3. 0
  4. 0
  5. 2

  裸的LCA,倍增算法。

code:

  1. //利用倍增算法求最近公共祖先
  2. //1.让结点a、b的深度相同
  3. //2.让节点a、b跳到LCA的下一层。
  4. #include<bits/stdc++.h>
  5. using namespace std;
  6. int n,m;
  7. int root;
  8. const int N=4e4+10,M=N*2;
  9. int h[N],e[M],ne[M],idx;
  10. int fa[N][16];
  11. int depth[N];
  12. void add(int a,int b){
  13. e[idx]=b,ne[idx]=h[a],h[a]=idx++;
  14. }
  15. int q[N];
  16. void bfs(){
  17. int hh=0,tt=0;
  18. memset(depth,0x3f,sizeof depth);
  19. q[0]=root;
  20. depth[0]=0;
  21. depth[root]=1;
  22. while(hh<=tt){
  23. int t=q[hh++];
  24. for(int i=h[t];~i;i=ne[i]){
  25. int j=e[i];
  26. if(depth[j]>depth[t]+1){
  27. depth[j]=depth[t]+1;
  28. q[++tt]=j;
  29. fa[j][0]=t;
  30. for(int k=1;k<=15;k++){
  31. fa[j][k]=fa[fa[j][k-1]][k-1];
  32. }
  33. }
  34. }
  35. }
  36. }
  37. int lca(int a,int b){
  38. if(depth[a]<depth[b]) swap(a,b);
  39. for(int i=15;i>=0;i--){
  40. if(depth[fa[a][i]]>=depth[b]){
  41. a=fa[a][i];
  42. }
  43. }
  44. if(a==b) return a;
  45. for(int i=15;i>=0;i--){
  46. if(depth[fa[a][i]]!=depth[fa[b][i]])
  47. {
  48. a=fa[a][i];
  49. b=fa[b][i];
  50. }
  51. }
  52. return fa[a][0];
  53. }
  54. signed main(){
  55. cin>>n;
  56. memset(h,-1,sizeof h);
  57. for(int i=1;i<=n;i++){
  58. int a,b;
  59. cin>>a>>b;
  60. if(b==-1) root=a;
  61. else add(a,b),add(b,a);
  62. }
  63. bfs();
  64. cin>>m;
  65. while(m--){
  66. int a,b;
  67. cin>>a>>b;
  68. int p=lca(a,b);
  69. if(p==a) cout<<1<<endl;
  70. else if(p==b) cout<<2<<endl;
  71. else cout<<0<<endl;
  72. }
  73. return 0;
  74. }

1171. 距离

给出 n 个点的一棵树,多次询问两点之间的最短距离。

注意:

  • 边是无向的。
  • 所有节点的编号是 1,2,…,n

输入格式

第一行为两个整数 n和 m。n表示点数,m 表示询问次数;

下来 n−1 行,每行三个整数x,y,k,表示点 x 和点 y 之间存在一条边长度为 k;

再接下来 m 行,每行两个整数x,y,表示询问点 x 到点 y 的最短距离。

树中结点编号从 1 到 n。

输出格式

共 m 行,对于每次询问,输出一行询问结果。

数据范围

2≤n≤1e4
1≤m≤2×1e4
0<k≤100
1≤x,y≤n

输入样例1:

  1. 2 2
  2. 1 2 100
  3. 1 2
  4. 2 1

输出样例1:

  1. 100
  2. 100

输入样例2:

  1. 3 2
  2. 1 2 10
  3. 3 1 15
  4. 1 2
  5. 3 2

输出样例2:

  1. 10
  2. 25

思路,对于树上的两个结点x,y间的距离我们可以用公式 dist[x],dist[y] ,dist[lca(x,y)] ,分别表示 x,y,以及x和y的最近公共祖先到根节点的距离。

那么distance=dist[x]+dist[y]-2*dist[lca(x,y)];

code:

  1. //树的tarjan算法离线求LCA
  2. //1.求出树中各点到根节点的距离
  3. //2.用tarjan算法将途中点分类,更新与u有关的节点的LCA更新dist
  4. //
  5. //
  6. #include<bits/stdc++.h>
  7. using namespace std;
  8. typedef pair<int, int> PII;
  9. const int N = 10010, M = N * 2;
  10. int n, m;
  11. int h[N], e[M], w[M], ne[M], idx;
  12. int dist[N];
  13. int p[N];
  14. int res[M];
  15. int st[N];
  16. vector<PII> query[N]; // first存查询的另外一个点,second存查询编号
  17. void add(int a, int b, int c)
  18. {
  19. e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
  20. }
  21. void dfs(int u, int fa)
  22. {
  23. for (int i = h[u]; ~i; i = ne[i])
  24. {
  25. int j = e[i];
  26. if (j == fa) continue;
  27. dist[j] = dist[u] + w[i];
  28. dfs(j, u);
  29. }
  30. }
  31. int find(int x)
  32. {
  33. if (p[x] != x) p[x] = find(p[x]);
  34. return p[x];
  35. }
  36. void tarjan(int u)
  37. {
  38. st[u] = 1;
  39. for (int i = h[u]; ~i; i = ne[i])
  40. {
  41. int j = e[i];
  42. if (!st[j])
  43. {
  44. tarjan(j);
  45. p[j] = u;
  46. }
  47. }
  48. for (auto item : query[u])
  49. {
  50. int y = item.first, id = item.second;
  51. if (st[y] == 2)
  52. {
  53. int anc = find(y);
  54. res[id] = dist[u] + dist[y] - dist[anc] * 2;
  55. }
  56. }
  57. st[u] = 2;
  58. }
  59. int main()
  60. {
  61. scanf("%d%d", &n, &m);
  62. memset(h, -1, sizeof h);
  63. for (int i = 0; i < n - 1; i ++ )
  64. {
  65. int a, b, c;
  66. scanf("%d%d%d", &a, &b, &c);
  67. add(a, b, c), add(b, a, c);
  68. }
  69. for (int i = 0; i < m; i ++ )
  70. {
  71. int a, b;
  72. scanf("%d%d", &a, &b);
  73. if (a != b)
  74. {
  75. query[a].push_back({b, i});
  76. query[b].push_back({a, i});
  77. }
  78. }
  79. for (int i = 1; i <= n; i ++ ) p[i] = i;
  80. dfs(1, -1);
  81. tarjan(1);
  82. for (int i = 0; i < m; i ++ ) printf("%d\n", res[i]);
  83. return 0;
  84. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/455166
推荐阅读
相关标签
  

闽ICP备14008679号