当前位置:   article > 正文

求LCA的四种方法(暴力,倍增,RMQ+ST,Tarjan)_lca算法

lca算法

目录

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

暴力

倍增法

RMQ+ST 

Tarjan

四个方法的优缺点比较

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

暴力

操作步骤:

  1. 求出每个结点的深度;
  2. 询问两个结点是否重合,若重合,则LCA已经求出;
  3. 否则,选择两个点中深度较大的一个,并移动到它的父亲。
  1. int LCA(int x,int y)
  2. {
  3. while(x!=y)
  4. {
  5. if(depth[x]>=depth[y]) x=fa[x];
  6. else y=fa[y];
  7. }
  8. return x;
  9. }

倍增法

 操作步骤:

  1. 求出倍增数组;
  2. 把两个点移动到同一深度;
  3. 逐步试探出LCA。
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. struct Edge
  4. {
  5. int to,next;
  6. }edge[500005*2];//无向图,两倍开
  7. int head[500005],grand[500005][21],depth[500005],lg[500001];
  8. int cnt,n,m,s;
  9. inline int read()
  10. {
  11. int x=0,f=1;char ch=getchar();
  12. while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
  13. while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
  14. return x*f;
  15. }
  16. void add(int x,int y)
  17. {
  18. edge[++cnt].to=y;
  19. edge[cnt].next=head[x];
  20. head[x]=cnt;
  21. }
  22. void dfs(int now,int fa)
  23. {
  24. depth[now]=depth[fa]+1;
  25. grand[now][0]=fa;
  26. for(int i=1;i<=lg[depth[now]];i++)
  27. //for(int i=1;(1<<i)<=depth[now];i++)
  28. grand[now][i]=grand[grand[now][i-1]][i-1];
  29. //爸爸的爸爸叫爷爷~~~
  30. for(int i=head[now];i;i=edge[i].next)
  31. //遍历和当前结点相连的所有的边(按输入的倒序),最后一条边的 edge[i].next==0
  32. {
  33. cout<<"第"<<i<<"条边,指向" <<edge[i].to<<endl;
  34. if(edge[i].to!=fa)
  35. dfs(edge[i].to,now);
  36. }
  37. }
  38. int LCA(int a,int b)
  39. {
  40. if(depth[a]<depth[b])
  41. swap(a,b);
  42. while(depth[a]>depth[b])
  43. a=grand[a][lg[depth[a]-depth[b]]-1];
  44. //倍增法逼近,e.g:depth[a]-depth[b]==14
  45. //lg[depth[a]-depth[b]]-1==3,a上升8个深度,depth[a]-depth[b]==6;
  46. //lg[depth[a]-depth[b]]-1==2,a上升4个深度,depth[a]-depth[b]==2;
  47. //lg[depth[a]-depth[b]]-1==1,a上升2个深度,depth[a]-depth[b]==0;
  48. if(a==b) return a;//a和b的LCA就是a
  49. for(int k=lg[depth[a]]-1;k>=0;k--)
  50. if(grand[a][k]!=grand[b][k])
  51. a=grand[a][k],b=grand[b][k];
  52. //从远古祖先(注意不要越界)中逐渐向最近的试探
  53. // e.g:depth[a]==14,depth[LCA]==7;
  54. // k=lg[depth[a]]-1,k==3;grand[a][k]==grand[b][k];continue;
  55. //k==2,grand[a][k]!=grand[b][k],a,b一起向上4个深度;
  56. //k==1,grand[a][k]!=grand[b][k],a,b一起向上2个深度;
  57. //k==0,grand[a][k]!=grand[b][k],a,b一起向上1个深度;
  58. //一共向上4+2+1==7个深度,找到LCA
  59. return grand[a][0];
  60. }
  61. int main()
  62. {
  63. n=read(),m=read(),s=read();
  64. for(int i=1;i<n;i++)
  65. {
  66. int a,b;
  67. a=read(),b=read();
  68. add(a,b);
  69. add(b,a);
  70. }
  71. for(int i=1;i<=n;i++)
  72. lg[i]=lg[i-1]+((1<<lg[i-1])==i);//log_{2}{i}+1
  73. dfs(s,0);//从根结点开始搜索
  74. while(m--)
  75. {
  76. int x,y;
  77. x=read(),y=read();
  78. printf("%d\n",LCA(x,y));
  79. }
  80. return 0;
  81. }


RMQ+ST 

 (转化为欧拉序列上的RMQ问题,采用ST算法)

 名词解释:

  • 欧拉序列:每经过一个结点,都进行一次统计产生的DFS序列;
  • RMQ:指的一类连续查询区间最小(最大)值的问题;
  • ST算法:求解RMQ问题的算法。

 不了解ST算法点这里:

 P3865 【模板】ST 表https://www.luogu.com.cn/problem/P3865

 ST表:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int n,m,f[100010][20];
  4. inline int read()
  5. {
  6. int x=0,f=1;char ch=getchar();
  7. while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
  8. while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
  9. return x*f;
  10. }
  11. int main()
  12. {
  13. cin>>n>>m;
  14. for(int i=1;i<=n;i++)
  15. f[i][0]=read();
  16. int t=log(n)/log(2);
  17. for(int j=1;j<=t;j++)
  18. for(int i=1;i<=n-(1<<j)+1;i++)
  19. f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
  20. while(m--)
  21. {
  22. int r,l;
  23. l=read(),r=read();
  24. int k=log(r-l+1)/log(2);
  25. int ans=max(f[l][k],f[r-(1<<k)+1][k]);
  26. printf("%d\n",ans);
  27. }
  28. return 0;
  29. }

 操作步骤:

  1. DFS求出欧拉序列和深度序列,以及每个结点在欧拉序列中第一次出现的位置;
  2. 找到查询的两个结点在欧拉序列中第一次出现的位置;
  3. 在深度序列中两个位置之间的区间找到深度最小的点。

        P.S.:假如两个结点在欧拉序列中不止出现一次,只需要任选其中一次来计算即可。

  1. //3.95s / 227.49MB / 1.67KB C++14 (GCC 9) O2
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. const int N=500005;
  5. vector<int>vec[N];
  6. //记录每个结点可以走向哪些结点
  7. int f[N*2][21],mem[N*2][21],depth[N*2],first[N],vis[N*2],lg[N];
  8. //f:记录深度序列区间中的最小深度
  9. //mem:记录 找到深度序列区间中的最小深度 时的对应结点(在欧拉序列中)
  10. //depth:在dfs过程中记录遍历到每个点时的对应深度
  11. //first:记录每个结点第一次出现时在欧拉序列中的位置
  12. //vis:欧拉序列
  13. //lg;lg[i]==log_{2}{i}+1
  14. int cnt=0,n,m,s;
  15. //cnt:每走到一个点计一次数
  16. inline int read()
  17. {
  18. int x=0,f=1;char ch=getchar();
  19. while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
  20. while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
  21. return x*f;
  22. }
  23. void dfs(int now,int dep)
  24. {
  25. if(!first[now]) first[now]=++cnt;//第一次遍历到该点
  26. depth[cnt]=dep,vis[cnt]=now;
  27. for(int i=0;i<vec[now].size();i++)
  28. {
  29. if(first[vec[now][i]]) continue;//是该结点的父节点,跳过
  30. else dfs(vec[now][i],dep+1);
  31. ++cnt;
  32. depth[cnt]=dep,vis[cnt]=now;//深搜完了vec[now][i]下的分支,回到当前结点 now
  33. }
  34. }
  35. void RMQ()
  36. {
  37. for(int i=1;i<=cnt;i++)
  38. {
  39. lg[i]=lg[i-1]+((1<<lg[i-1])==i);
  40. f[i][0]=depth[i];//区间长度为1时,该区间内深度的最小值就是该结点的深度
  41. mem[i][0]=vis[i];
  42. }
  43. for(int j=1;(1<<j)<=cnt;j++)//枚举的区间长度倍增
  44. for(int i=1;i+(1<<j)-1<=cnt;i++)//枚举合法的每个区间起点
  45. {
  46. if(f[i][j-1]<f[i+(1<<(j-1))][j-1])//深度最小的点在前半个区间
  47. {
  48. f[i][j]=f[i][j-1];
  49. mem[i][j]=mem[i][j-1];
  50. }
  51. else//深度最小的后半个区间
  52. {
  53. f[i][j]=f[i+(1<<(j-1))][j-1];
  54. mem[i][j]=mem[i+(1<<(j-1))][j-1];
  55. }
  56. }
  57. }
  58. int ST(int x,int y)
  59. {
  60. int l=first[x],r=first[y];//找到输入的两个结点编号对应在欧拉序列中第一次出现的位置
  61. if(l>r) swap(l,r);
  62. int k=lg[r-l+1]-1;
  63. if(f[l][k]<f[r-(1<<k)+1][k]) return mem[l][k];
  64. else return mem[r-(1<<k)+1][k];
  65. }
  66. int main()
  67. {
  68. n=read(),m=read(),s=read();
  69. for(int i=1;i<n;i++)
  70. {
  71. int a,b;
  72. a=read(),b=read();
  73. vec[a].push_back(b);
  74. vec[b].push_back(a);
  75. }
  76. dfs(s,0);//打表,给first、depth、vis赋值,给RMQ奠定基础
  77. /*cout<<"各结点第一次出现的位置:"<<endl;
  78. for(int i=1;i<=n;i++)
  79. cout<<first[i]<<' ';
  80. cout<<endl;
  81. cout<<"欧拉序列:"<<endl;
  82. for(int i=1;i<=2*n;i++)
  83. cout<<vis[i]<<' ';
  84. cout<<endl;
  85. cout<<"深度序列"<<endl;
  86. for(int i=1;i<=2*n;i++)
  87. cout<<depth[i]<<' ';
  88. cout<<endl;*/
  89. RMQ();//打表,给f和mem赋值 ,给ST奠定基础
  90. while(m--)
  91. {
  92. int x,y;
  93. x=read(),y=read();
  94. printf("%d\n",ST(x,y));
  95. }
  96. return 0;
  97. }

 

 算法效率:

预处理时间复杂度:O(nlog_{2} {n})

单次询问时间复杂度:O(1)

总时间复杂度:O(nlog_{2}{n}+q)

空间复杂度:O(nlog_{2}{n})


Tarjan

 (这还没学,随便写写)

操作步骤:

  1. DFS整棵树。每个结点x一开始属于只有该结点本身的集合S_x
  2. DFS(x)时,每次访问子树y时,把S_y合并到S_x
  3. x的所有子结点访问完,标记x为已访问;
  4. 遍历所有关于x的询问(x,y), 如果y已被访问,则这个询问的答案为并查集中的Find(y)。

  1. void dfs(int x)
  2. {
  3. for(int i=0;i<g[x].size();i++)
  4. {
  5. dfs(g[x][i]);
  6. uni(g[x][i],x);
  7. }
  8. vis[x]=1;
  9. for(int i=0;i<query[x].size();i++)
  10. {
  11. int y=query[x][i];
  12. if(vis[y]) ans[x][y]=find(y);
  13. }
  14. }

四个方法的优缺点比较:

(n个点,q次询问)

P8805 [蓝桥杯 2022 国 B] 机房 

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int N=2e5+10;
  4. int nex[N],head[N],ver[N],val[N],n,m,x,y,tot,f[N][31],depth[N],sum[N][31],lg[N];
  5. void add(int x,int y)
  6. {
  7. ver[++tot]=y;
  8. nex[tot]=head[x];
  9. head[x]=tot;
  10. val[x]++;
  11. }
  12. void dfs(int u,int fa)
  13. {
  14. depth[u]=depth[fa]+1;
  15. f[u][0]=fa;
  16. sum[u][0]=val[u];
  17. for(int i=1;i<31;i++)
  18. {
  19. f[u][i]=f[f[u][i-1]][i-1];
  20. sum[u][i]=sum[f[u][i-1]][i-1]+sum[u][i-1];
  21. }
  22. for(int i=head[u];i;i=nex[i])
  23. if(ver[i]!=fa) dfs(ver[i],u);
  24. }
  25. int lca(int x,int y)
  26. {
  27. if(depth[x]>depth[y]) swap(x,y);
  28. int tmp=depth[y]-depth[x],ans=0;
  29. for(int j=0;tmp;j++,tmp>>=1)
  30. if(tmp&1) ans+=sum[y][j],y=f[y][j];
  31. if(y==x) return ans+val[y];
  32. for(int j=30;j>=0&&y!=x;j--)
  33. {
  34. if(f[x][j]!=f[y][j])
  35. {
  36. ans+=sum[x][j]+sum[y][j];
  37. x=f[x][j];y=f[y][j];
  38. }
  39. }
  40. ans+=sum[x][0]+sum[y][0]+sum[f[x][0]][0];
  41. return ans;
  42. }
  43. int main()
  44. {
  45. cin>>n>>m;
  46. for(int i=1;i<n;i++)
  47. {
  48. scanf("%d%d",&x,&y);
  49. add(x,y);
  50. add(y,x);
  51. }
  52. dfs(1,0);
  53. while(m--)
  54. {
  55. scanf("%d%d",&x,&y);
  56. printf("%d\n",lca(x,y));
  57. }
  58. return 0;
  59. }

 

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

闽ICP备14008679号