LCA目前比较流行的算法主要有tarjian,倍增和树链剖分

1)tarjian

是一种离线算法,需要提前知道所有询问对

算法如下

  1.读入所有询问对(u,v),并建好树(建议邻接表)

  2.初始化每个节点各属一个并查集,都指向自己

  3.对整棵树进行dfs(深度优先搜索)遍历

每处理到一个新节点(u)时看他的另一半(询问对象v)是否visit过,如果visit过了,则这组询问对的lca即v的并查集的根节点,若没有visit过,则继续向下深搜,该节点记为已visit

每当回溯的时候都将子节点的并查集并到父节点的并查集中

这样一遍走下来就完成了tarjian算法。

超详细tarjain:orz


2)树上倍增

f[i,j]表示i的第2^j祖先dfs预处理f[i,j]=f[f[i,j-1],j-1];

对于每一对x,y先将深度调成一样再枚举j逐一往上找,这两个过程都是log的

超详细树上倍增:orz


3)树剖

树剖(树链剖分)是一种在线算法,跑起来非常快,应该是目前LCA算法中最优的

建树后,我们需要把整棵树划为轻重链,

每一个非叶子节点都一定在一条重链上

定义:

重边:父节点与其子树最大(子节点最多)的节点的连边称为重边

轻边:非重边即为轻边

重链:相连的重边称为重链

划分重链后,我们要记一个jump数组表示存每个节点的“跳”的信息

如果这个节点在重链上,则jump[i]为它所属重链的根节点(最顶端)

如果这个节点不在重链上或者它是一条重链的顶端(根节点),那么jump[i]为它的父节点

接下来我们就可以处理询问对了

比如求两个节点a,b的LCA

我们先看他们是否在同一条重链上,如果是,则LCA即为深度较小的节点

如果不是,则我们需要比较jump[a]和jump[b]的深度,jump[a]比较浅则令a=jump[a]反之令b=jump[b]

重复以上过程直到a==b(LCA为这个节点)或a,b在同一条重链上时(LCA为深度浅的节点)

这样就完成了,复杂度虽说评是O(n*logn)但实际上跑起来快得多

超详细树剖:orz


练手题

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

题目描述

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

输入输出格式

输入格式:

第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。

接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。

输出格式:

输出包含M行,每行包含一个正整数,依次为每一个询问的结果。

输入输出样例

输入样例#1:

  1. 5 5 4
  2. 3 1
  3. 2 4
  4. 5 1
  5. 1 4
  6. 2 4
  7. 3 2
  8. 3 5
  9. 1 2
  10. 4 5
输出样例#1:

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

说明

时空限制:1000ms,128M

数据规模:

对于30%的数据:N<=10,M<=10

对于70%的数据:N<=10000,M<=10000

对于100%的数据:N<=500000,M<=500000

样例说明:

该树结构如下:

第一次询问:2、4的最近公共祖先,故为4。

第二次询问:3、2的最近公共祖先,故为4。

第三次询问:3、5的最近公共祖先,故为1。

第四次询问:1、2的最近公共祖先,故为4。

第五次询问:4、5的最近公共祖先,故为4。

故输出依次为4、4、1、4、4。

LCA板子!!!

1)tarjan

  1. #include <iostream>
  2. #include <cstring>
  3. #include <cstdio>
  4. using namespace std;
  5.  
  6. const int N = ;
  7. const int M = ;
  8. int top,cnt,dad[N],ans[N];
  9. bool used[N];
  10.  
  11. struct heads {
  12. int head;
  13. }v1[N],v2[N];
  14.  
  15. struct Edge {
  16. int v,next,num;
  17. }e1[M],e2[M];
  18.  
  19. void chu()
  20. {
  21. memset(v1,-,sizeof(v1));
  22. memset(v2,-,sizeof(v2));
  23. memset(dad,-,sizeof(dad));
  24. memset(used,,sizeof(used));
  25. }
  26.  
  27. int getdad(int x)
  28. {return dad[x] == - ? x : dad[x] = getdad(dad[x]);}
  29.  
  30. void Unions(int a,int b)
  31. {
  32. int r1=getdad(a);
  33. int r2=getdad(b);
  34. if(r1!=r2)
  35. dad[r2]=r1;
  36. }
  37.  
  38. void add1(int u,int v)
  39. {
  40. e1[top].v=v;
  41. e1[top].next=v1[u].head;
  42. v1[u].head=top++;
  43. }
  44.  
  45. void add2(int u,int v,int i)
  46. {
  47. e2[cnt].num=i;
  48. e2[cnt].v=v;
  49. e2[cnt].next=v2[u].head;
  50. v2[u].head=cnt++;
  51. }
  52.  
  53. void Tarjan(int u)
  54. {
  55. used[u]=true;
  56. for(int i=v1[u].head;i!=-;i=e1[i].next)
  57. {
  58. int v=e1[i].v;
  59. if(used[v])
  60. continue;
  61. Tarjan(v);
  62. Unions(u,v);
  63. }
  64. int Ms;
  65. for(int i=v2[u].head;i!=-;i=e2[i].next)
  66. {
  67. int v=e2[i].v;
  68. Ms=e2[i].num;
  69. if(used[v])///==getdad(v)
  70. ans[Ms]=getdad(v);
  71. }
  72. }
  73.  
  74. int main()
  75. {
  76. int n,m,s,u,v;
  77. scanf("%d%d%d",&n,&m,&s);
  78. chu();
  79. int nn=n;
  80. n--;
  81. while(n--)
  82. {
  83. scanf("%d%d",&u,&v);
  84. add1(u,v),add1(v,u);
  85. }
  86. for(int i=;i<=m;i++)
  87. {
  88. scanf("%d%d",&u,&v);
  89. add2(u,v,i),add2(v,u,i);
  90. }
  91. Tarjan(s);
  92. for(int i=;i<=nn;i++)
  93. printf("%d\n",ans[i]);
  94. return ;
  95. }

Tarjan版(无注释...)

2)树上倍增

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cmath>
  4. //maybe my English is not very good
  5.  
  6. using namespace std;
  7.  
  8. const int M = 5e5 + ;
  9. int n,m,s;
  10. int num;
  11. int deep[M],h[M];
  12. bool vs[M];
  13. int jumps[M][];
  14. int p;
  15.  
  16. struct A{
  17. int next;
  18. int to;
  19. }t[M<<];
  20.  
  21. inline int read() //optimize
  22. {
  23. int x=,f=;char ch=getchar();
  24.  
  25. while(ch<''||ch>'')
  26. {
  27. if(ch=='-') f=-;
  28. ch=getchar();
  29. }
  30.  
  31. while(ch>=''&&ch<='')
  32. {
  33. x=x*+ch-'';
  34. ch=getchar();
  35. }
  36.  
  37. return x*f;
  38. }
  39.  
  40. void ADD(int x,int y) //connect the x and the y
  41. {
  42. num++;
  43. t[num].to=y;
  44. t[num].next=h[x];
  45. h[x]=num;
  46. }
  47.  
  48. void Dfs(int u)
  49. {
  50. for(int i=h[u];i!=-;i=t[i].next)
  51. {
  52. int v=t[i].to; //u's next side
  53. if(deep[v] == ) //if v is not visited
  54. {
  55. deep[v]=deep[u]+; //deep+1
  56. jumps[v][]=u; //u is v's dad
  57. Dfs(v); //continue Dfs
  58. }
  59. }
  60. }
  61.  
  62. void steps()
  63. {
  64. p=int(log(n)/log()+0.001); //find the biggest
  65. for(int i=;i<=p;i++) //the Limit
  66. for(int j=;j<=n;j++)
  67. jumps[j][i]=jumps[jumps[j][i-]][i-];
  68. //the j jump 2^i can get to the (first jump 2^(i-1),then jump 2^i-1 can get to)
  69. //eh...I will speak in Chinese.
  70. //because 倍增 is use 次方的形式 increase
  71. }
  72.  
  73. int LCA(int a,int b)
  74. {
  75. //We let the b's deep is small
  76. if(deep[a]<deep[b]) swap(a,b);
  77. for(int i=p;i>=;i--)
  78. {//first let the a jump to the b's deep
  79. if(deep[jumps[a][i]]>=deep[b])
  80. a=jumps[a][i];
  81. }
  82. if(a == b) return b; //if the b is them's LCA , return b
  83. for(int i=p;i>=;i--) //jump together
  84. {
  85. if(jumps[a][i]!=jumps[b][i])
  86. a=jumps[a][i],b=jumps[b][i]; //update
  87. }
  88. return jumps[a][];
  89. }
  90.  
  91. int main()
  92. {
  93. //s is the root
  94. n=read();m=read();s=read();
  95. for(int i=;i<=n;i++) h[i]=-;
  96. int x,y;
  97. for(int i=;i<n;i++)
  98. {
  99. x=read();y=read();
  100. //connect the x and the y
  101. ADD(x,y);
  102. ADD(y,x);
  103. }
  104. deep[s]=; //this is too important !!!
  105. //if you don't think so ,"//" it.
  106. //and then you will know
  107. Dfs(s); //Dfs the root(s)
  108. steps(); //find the steps
  109. int a,b;
  110. while(m--)
  111. {
  112. a=read();b=read();
  113. printf("%d\n",LCA(a,b));
  114. }
  115. return ;
  116. }

树上倍增英文版???

  1. #include<iostream>
  2. #include<cmath>
  3. #include<algorithm>
  4. #include<cstring>
  5. #include<cstdio>
  6. #include<stdio.h>
  7. #include<vector>
  8. #define maxn 500500
  9. using namespace std;
  10. ///隶属邻接表
  11. struct Edge{ //邻接表的结构体
  12. int from,to;
  13. }edges[*maxn]; //边要乘2,因为是无向图 ;
  14. int first[maxn],next[*maxn]; //同理;
  15. int read(){ //读入优化,可以照着这个模板来写,这个还算写的比较好看。
  16. int re=;
  17. char ch=getchar();
  18. while (ch<'' || ch>'') ch=getchar();
  19. while (ch>='' && ch<=''){
  20. re=re*+ch-'';
  21. ch=getchar();
  22. }
  23. return re;
  24. }
  25. ///////////////////////////////////////////////
  26. ///全局变量
  27. int n,m;
  28. int root;
  29. int height[maxn];
  30. float log2n;
  31. ///////////////////////////////////////////////////////
  32. ///隶属LCA的全局变量
  33. int f[maxn][];//
  34. int have[maxn]; //have,有没有找过,这都是套路 。
  35. void dfs(int u,int h){ //u代表点的标号,h代表高度。
  36. int v;
  37. height[u]=h;
  38. for(int i=;i<=log2n;i++) {
  39. if(h<=(<<i)) break; //由于i是从小到大计算的,故(1<<i)>=h 时可直接退出。请务必想清楚是<= 还是=。
  40. f[u][i] = f[ f[u][i-] ][i-]; //动规计算。同样也是一切倍增算法的核心。
  41. }
  42. int k=first[u];
  43. while(k!=-){
  44. v=edges[k].to;
  45. if(!have[v]) {
  46. have[v]=;
  47. f[v][]=u; //将要找的下一个点的父节点标为当前处理的节点u。
  48. dfs(v,h+);
  49. }
  50. k=next[k];
  51. }
  52. }
  53. int require_LCA(int a,int b){
  54. int da=height[a],db=height[b];
  55. //第一步,将a,b两点移到同样的高度,只动高度大的那个点而不动高度小的那个点。
  56. if(da!=db) {
  57. if(da<db){ //保证a的高度是大于b的高度的。
  58. swap(a,b);
  59. swap(da,db);
  60. }
  61. int d=da-db;
  62. for(int i=;i<=log2n;i++)
  63. if( (<<i) & d) a=f[a][i]; //这里的位运算可以减少代码量
  64. //考虑到d是一个定值,而(1<<i)在二进制中只有第(i+1)位是1;
  65. //那么d与(1<<i)如果某一位为1,那么表示可以向上移动,
  66. //如果此时不移动,那么i增大了后就无法使height[a]==height[b]了
  67. }
  68. //第二步,找到某个位置i,在这个位置时,f[a][i]!=f[b][i],但再向上移动一步,a,b相同了
  69. //从log2n开始从大到小枚举i,如果超过了a,b的高度,则令i继续减小
  70. //如果没有超过a,b的高度,那么就判断移动了后会不会让a==b,
  71. //是,则i继续减小,否则,令此时的a=f[a][i],b=f[b][i];
  72. if(a==b) return b;
  73. int i=;
  74. for(i=log2n;i>=;i--) {
  75. if(height[ f[a][i] ]<) continue;
  76. if( f[a][i]==f[b][i] ) continue;
  77. else a=f[a][i],b=f[b][i]; //顺便一提,在第二步任何地方没有break;
  78. //我就是因为在这里写了一个break,然后找了我两个小时啊。
  79. }
  80. return f[a][];
  81. }
  82. /////////////////////////////////
  83. ///据说从主函数开始阅读是个好习惯。
  84. int main(){
  85. // freopen("in2.txt","r",stdin);
  86. n=read();m=read();root=read();
  87. memset(first,-,sizeof(first));
  88. memset(next,-,sizeof(next));
  89. int s,t;
  90. int dsd=*(n-);
  91. for(int i=;i<=dsd;i+=) {
  92. s=read();t=read(); //读入优化。
  93. edges[i].from=s;
  94. edges[i].to=t;
  95. edges[i+].from=t;
  96. edges[i+].to=s;
  97. next[i]=first[s];
  98. first[s]=i;
  99. next[i+]=first[t];
  100. first[t]=i+;
  101. }
  102. // 以上是邻接表,在此不再赘述。
  103. log2n=log(n)/log()+; //C++计算log是自然对数,我们要用的以2为底的对数,故要除以log(2);
  104. //对无理数加上1或是0.5是个好习惯,可以减小误差;
  105. memset(have,,sizeof(have));
  106. memset(height,,sizeof(height));
  107. memset(f,-,sizeof(f));
  108. have[root]=; //fa[][]和height[]要在dfs理进行计算,不然根本找不到某个非根节点的父亲是谁;
  109. dfs(root,);
  110. for(int i=;i<=n;i++){
  111. for(int j=;j<=log2n;j++) {
  112. if(height[i] <=(<<j) ) break;
  113. }
  114. }
  115. for(int i=;i<m;i++) { //应对要求进行求解。
  116. s=read();t=read();
  117. int y=require_LCA(s,t);
  118. printf("%d\n",y);
  119. }
  120. return ;
  121. }

中文版23333

3)树剖

  1. #include <iostream>
  2. #include <cstdio>
  3. #define _(ch) ch=read() //便于读入
  4.  
  5. using namespace std;
  6.  
  7. const int S = ;
  8. bool f[S]; //dfs 标记
  9. int n,m,s;
  10. int fa[S]; //并查集
  11. int num,h[S]; //邻接表
  12. int deep[S]; //深度
  13. int sum[S]; //子结点个数
  14. int dad[S]; //链头元素
  15.  
  16. struct B{
  17. int to,next;
  18. }t[S<<];
  19.  
  20. inline int read() //optimize
  21. {
  22. int x=,f=;char ch=getchar();
  23.  
  24. while(ch<''||ch>'')
  25. {
  26. if(ch=='-') f=-;
  27. ch=getchar();
  28. }
  29.  
  30. while(ch>=''&&ch<='')
  31. {
  32. x=x*+ch-'';
  33. ch=getchar();
  34. }
  35.  
  36. return x*f;
  37. }
  38.  
  39. void ADD(int x,int y) //connect the x and the y
  40. {
  41. num++;
  42. t[num].to=y;
  43. t[num].next=h[x];
  44. h[x]=num;
  45. }
  46.  
  47. inline int Find(int x)
  48. //find the root (重链's top)
  49. {return fa[x] == x ? x : fa[x] = Find(fa[x]);}
  50.  
  51. inline void Unions(int a,int b)
  52. { //union(搭重链)
  53. /*int f1=Find(a);
  54. int f2=Find(b);
  55. if(f1!=f2)
  56. {
  57. fa[f1]=f2;
  58. }*/
  59. fa[Find(b)]=Find(a);
  60. }
  61.  
  62. inline void dfs(int p)//D is the (结点)
  63. {//calc every D's son D
  64. //每个结点的深度
  65. f[p]=true;
  66. int maxx=; //寻找子节点中拥有子结点个数最多的节点编号
  67. sum[]=-; //0号没有子结点
  68. for(int j=h[p];j;j=t[j].next)
  69. { //进行遍历
  70. int v=t[j].to;
  71. if(f[v]) continue;
  72. deep[v]=deep[p]+;
  73. dad[v]=p; //p is v's dad
  74. dfs(v); //continue dfs
  75. if(sum[v] > sum[maxx]) maxx=v; //update
  76. sum[p]+=sum[v]+;
  77. // p的子结点数 = p 的以'v'为根的子树的结点数目加上'v'这个点(即+1)
  78. }
  79. if(maxx) Unions(p,maxx); //if updated
  80. //that means find the (重链) succeed
  81. }
  82.  
  83. inline int jump(int p) //find p can jump to
  84. {
  85. int top=Find(p); //(重链)'s top
  86. if(top == p) return dad[p];
  87. // 如果p所处于的链的链头就是自己,也就是说,已经位于链的top处,所以只能够跳到他的父结点的位置,
  88. // 所以直接return it's dad,即跳一步到达父结点处
  89. // 说白了就是说,一定要跳!!!
  90. return top; //其余情况就返回链头就好(就是当前结点跳到了链头位置)
  91. }
  92.  
  93. inline int Lca(int a,int b) //Lca
  94. {
  95. while(a!=b) //当两点不相等的时候就开始跳
  96. {
  97. if(Find(a)==Find(b)) //如果它们位于同一条重链上
  98. return deep[a]<deep[b] ? a:b; //直接返回深度较浅的那个点
  99. int ja=jump(a),jb=jump(b);
  100. if(deep[ja] > deep[jb]) //如果a跳了之后没有到达b跳了之后的深度
  101. a=ja; //就选取深度较深的点跳
  102. else
  103. b=jb;
  104. }
  105. return a;
  106. }
  107.  
  108. int main()
  109. {
  110. _(n),_(m),_(s);
  111. int x,y;
  112. for(int i=;i<n;i++)
  113. {
  114. _(x),_(y);
  115. ADD(x,y),ADD(y,x);
  116. fa[i]=i; //顺便初始化一下并查集
  117. }
  118. fa[n]=n; //有一个没有进行初始化的并查集,进行初始化
  119. deep[s]=; //根结点的深度设置为1,非常重要!!!!
  120. dfs(s); //寻找子节点个数,位于哪一条重链上
  121. while(m--)
  122. {
  123. _(x),_(y);
  124. printf("%d\n",Lca(x,y));
  125. }
  126. return ;
  127. }

混杂版(才不是英语不好!)

【テンプレート】LCA的更多相关文章

  1. 【BZOJ3626】LCA(树链剖分,Link-Cut Tree)

    [BZOJ3626]LCA(树链剖分,Link-Cut Tree) 题面 Description 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1. ...

  2. 【&】位与运算符【|】位或运算符之权限控制算法

    [&]位与运算符: 按位与运算符"&"是双目运算符. 其功能是参与运算的两数各对应的二进位相与.只有对应的两个二进位均为1时,结果位才为1 ,否则为0.参与运算的数 ...

  3. 存储过程 分页【NOT IN】和【>】效率大PK 千万级别数据测试结果

    use TTgoif exists (select * from sysobjects where name='Tonge')drop table Tongecreate table Tonge( I ...

  4. 普通方式 分页【NOT IN】和【>】效率大PK 千万级别数据测试结果

    首现创建一张表,然后插入1000+万条数据,接下来进行测试. use TTgoif exists (select * from sysobjects where name='Tonge')drop t ...

  5. java byte【】数组与文件读写(增加新功能)

    今天在测试直接写的文章: java byte[]数组与文件读写 时,想调用FileHelper类对字节数组以追加的方式写文件,结果无论怎样竟然数据录入不全,重新看了下文件的追加模式,提供了两种方式: ...

  6. Spring 当 @PathVariable 遇上 【. # /】等特殊字符

    @PathVariable注解应该不是新鲜东西了Spring3.0就开始有了 URL中通过加占位符把参数传向后台 举个栗子,如下比较要说的内容比较简单就大概齐的写一下 画面侧 $.ajax({ typ ...

  7. 【php正则】php正则匹配UTF-8格式的中文汉字 和 【,】【,】【。】等符号

    1.php正则匹配UTF-8格式的中文汉字 和 [,][,][.]等符号 if (preg_match_all("/([\x{4e00}-\x{9fa5}]+((,)?)+((,)?)+(( ...

  8. 公式中表达单个双引号【"】和空值【""】的方法及说明

    http://club.excelhome.net/thread-661904-1-1.html 有人问为什么不用三个双引号"""来表示单个双引号["]呢,如果 ...

  9. ubuntu fcitx google 输入法打不出中括号【】

    编辑/usr/share/fcitx/data/punc.mb.zh_CN, 将 [ · ] 「 」 这部分改成自己习惯的: [  [ ]  ] 保存后,重启一下fcitx就OK了.

随机推荐

  1. 测开之路九十四:css之盒子模型

    盒子模型 为了演示方便,把内容放到盒子里面 引用css 演示内容 外边距: 4个方向分开写 简写为一条指令,顺序为上右下左 简写为一条指令,第一个值为上下,第二个值为左右 简写为一条指令,只有一个值时 ...

  2. .net refactor 命令行

    VS中设置项目的编译后事件命令(此命令会在程序集生成后自动在原位置加密,覆盖原来的程序集): "C:\Program Files (x86)\Eziriz\.NET Reactor\dotN ...

  3. Nginx基本属性配置

    Nginx基本属性配置 1.找到安装目录下conf 文件下的nginx.conf文件 通过 Notepad++打开进行 属性配置   image ==>   image 2.worker_pro ...

  4. 应用安全-Web安全-子域名/相关域名

    技巧 DNS解析记录 主站获取 单点登录接口 crossdomain.xml IP反查 通过HTTPS证书收集 DNS域传送搜集 联系人信息/邮箱反查域名 x-dns-prefetch-control ...

  5. 计算距离的SQL语句

    一,BEGINset @num=6378.138*2*ASIN(SQRT(POW(SIN((lat1*PI()/180-lat2*PI()/180)/2),2)+COS(lat1*PI()/180)* ...

  6. h2内嵌数据库使用

    参考文档 1 https://www.cnblogs.com/xdp-gacl/p/4171024.html 参考文档 2 https://blog.csdn.net/mafan121/article ...

  7. Sentinel分布式系统的流量防卫兵

    Sentinel 是什么?官网:https://github.com/alibaba/Sentinel/wiki/介绍 随着微服务的流行,服务和服务之间的稳定性变得越来越重要.Sentinel 以流量 ...

  8. Java RPC 分布式框架性能大比拼,Dubbo排老几?

    来源:http://985.so/aXe2 Dubbo 是阿里巴巴公司开源的一个Java高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成 ...

  9. [2019杭电多校第二场][hdu6598]Harmonious Army(最小割)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6598 题意是说一个军队有n人,你可以给他们每个人安排战士或者法师的职业,有m对人有组合技,组合技的信息 ...

  10. SCUT - 31 - 清一色 - dfs

    https://scut.online/p/31 还是不知道为什么RE了.的确非常玄学. 重构之后就没问题了.果然写的越复杂,分的情况越乱就越容易找不到bug. #include<bits/st ...