首先,什么是LCA?

LCA:最近公共祖先

祖先:从当前点到根节点所经过的点,包括他自己,都是这个点的祖先

A和B的公共祖先:同时是A,B两点的祖先的点

A和B的最近公共祖先:深度最大的A和B的公共祖先

树上倍增:预处理nlog2n       求解nlog2n


原理大体描述:两个点都往上找,找到的第一个相同的点,就是他们的LCA

这里会有两个问题:

Q1:若两个点深度不同,可能会错开

Q2:若真一个一个往上找,时间太慢

对于Q1,如果两个点深度不同,而我们又需要它们深度相同,那就想办法让他们深度相同就行了呗,让更深的先跳到和另一个点深度一样,具体看代码

对于Q2,我们就要看标题了,很明显,用倍增可以缩减时间


原理讲得差不多了,是时候说怎么做了,实在不懂,代码有注释QWQ

首先,深搜一遍,目的是处理每个点的深度和f[i][j],深度用deap[]记录,f[i][j]的含义是i点向上跳2j个点所到达的点,在此之前,先处理log[i](跳i个点的j是多少,j就是前面提到的2j的j)

代码:

  1. 1 for(rll i=1;i<=n;++i)
  2. 2 {
  3. 3 lg[i]=lg[i-1]+(1<<lg[i-1]==i);//(1<<lg[i-1])先进行,然后判断是否相等
  4. 4 //如果相等,就说明(1<<lg[i-1])能跳到这里 lg[i]=log2(i)
  5. 5 }

  1. 1 oid dfs(ll u,ll fat)//u 子节点 fat u的父节点
  2. 2 {
  3. 3 f[u][0]=fat;//u向上跳1<<0(=1)个点就是父节点
  4. 4 deap[u]=deap[fat]+1;//深度为父节点+1
  5. 5 for(rll i=1;i<=lg[deap[u]];++i)//更新f数组
  6. 6 {
  7. 7 f[u][i]=f[f[u][i-1]][i-1];//倍增算法
  8. 8 }
  9. 9 for(rll i=head[u];i;i=e[i].nxt)//遍历边
  10. 10 {
  11. 11 ll v=e[i].v;
  12. 12 if(v!=fat)//判断到达的点是否是父节点(毕竟不能绕回去)
  13. 13 {
  14. 14 dfs(v,u);//继续搜索
  15. 15 }
  16. 16 }
  17. 17 }

然后,比较两点的深度,操作就是让深的跳到浅的,使得两点深度一样

代码:

  1. 1 if(deap[u]<deap[v])//保证u的深度比v大
  2. 2 {
  3. 3 u=u^v,v=u^v,u=u^v;//相当于swap(u,v); ^ 异或符号
  4. 4 }
  5. 5 while(deap[u]>deap[v])//如果两点深度不同,那就让深度大的点跳到和另一个点的深度
  6. 6 {
  7. 7 u=f[u][lg[deap[u]-deap[v]]-1];//更新u
  8. 8 //为什么-1,个人理解,做事要留有余地
  9. 9 }

现在,一样深了,此时如果A和B是同一个点了,那这个点就是他们的LCA,直接返回结束即可

  1. 1 if(u==v) return u;//如果是一个点,直接返回

否则,继续向下进行

两点同时向上跳,如果两点跳后仍不同,继续跳,同时更新值,如果相同,这里不确定该点是否是两点的LCA,因此,不更新值,将距离调小,继续跳(说白了就是不让他们相同),最后,他们肯定会跳到他们的LCA的孩子上(因为不让他们相等,距离又在不断减小,他们会距离LCA越来越近),返回当前点的父亲即可

代码:

  1. 1 for(rll i=lg[deap[u]]-1;i>=0;i--)//继续向上跳
  2. 2 {
  3. 3 if(f[u][i]!=f[v][i])//如果他们没碰面
  4. 4 {
  5. 5 u=f[u][i],v=f[v][i];//更新数值,继续跳
  6. 6 }
  7. 7 }
  8. 8 return f[u][0];//返回

完整代码:

  1. 1 #include<bits/stdc++.h>
  2. 2 #define ll long long
  3. 3 #define rll register long long
  4. 4 using namespace std;
  5. 5 const ll N=5e5+5;
  6. 6 ll n,m,s,cnt;
  7. 7 struct edge
  8. 8 {
  9. 9 ll u,v,nxt;
  10. 10 };
  11. 11 edge e[N<<1];//边表存树
  12. 12 ll head[N],deap[N],f[N][20],lg[N];
  13. 13 //head 记录该点发出的最后一条边 deap 该点的深度
  14. 14 //f[i][j] 第i号点向上跳(1<<j)个点后到达的点 lg 记录log,节约时间
  15. 15 inline ll read()//快读模板
  16. 16 {
  17. 17 ll x=0;
  18. 18 bool flag=false;//判断是否是负数
  19. 19 char ch=getchar();
  20. 20 while(ch<'0'||ch>'9')
  21. 21 {
  22. 22 if(ch=='-') flag=true;
  23. 23 ch=getchar();
  24. 24 }
  25. 25 while(ch>='0'&&ch<='9')
  26. 26 {
  27. 27 x=(x<<3)+(x<<1)+ch-'0';
  28. 28 //(x<<3)左移,相当于x乘8,(x<<1)相当于x乘2,乘法结合律,x乘了10
  29. 29 ch=getchar();
  30. 30 }
  31. 31 if(flag) return ~(x-1);//是负数,减1取反
  32. 32 return x;//是正数,直接输出
  33. 33 }//快读
  34. 34 void add(ll u,ll v)//建边
  35. 35 {
  36. 36 e[++cnt].u=u;//起始点
  37. 37 e[cnt].v=v;//终点
  38. 38 e[cnt].nxt=head[u];//记录边
  39. 39 head[u]=cnt;//更新最后的边
  40. 40 }
  41. 41 void dfs(ll u,ll fat)//u 子节点 fat u的父节点
  42. 42 {
  43. 43 f[u][0]=fat;//u向上跳1<<0(=1)个点就是父节点
  44. 44 deap[u]=deap[fat]+1;//深度为父节点+1
  45. 45 for(rll i=1;i<=lg[deap[u]];++i)//更新f数组
  46. 46 {
  47. 47 f[u][i]=f[f[u][i-1]][i-1];//倍增算法
  48. 48 }
  49. 49 for(rll i=head[u];i;i=e[i].nxt)//遍历边
  50. 50 {
  51. 51 ll v=e[i].v;
  52. 52 if(v!=fat)//判断到达的点是否是父节点(毕竟不能绕回去)
  53. 53 {
  54. 54 dfs(v,u);//继续搜索
  55. 55 }
  56. 56 }
  57. 57 }//搜索,填f数组
  58. 58 ll lca(ll u,ll v)
  59. 59 {
  60. 60 if(deap[u]<deap[v])//保证u的深度比v大
  61. 61 {
  62. 62 u=u^v,v=u^v,u=u^v;//相当于swap(u,v); ^ 异或符号
  63. 63 }
  64. 64 while(deap[u]>deap[v])//如果两点深度不同,那就让深度大的点跳到和另一个点的深度
  65. 65 {
  66. 66 u=f[u][lg[deap[u]-deap[v]]-1];//更新u
  67. 67 //为什么-1,个人理解,做事要留有余地
  68. 68 }
  69. 69 if(u==v) return u;//如果是一个点,直接返回
  70. 70 for(rll i=lg[deap[u]]-1;i>=0;i--)//继续向上跳
  71. 71 {
  72. 72 if(f[u][i]!=f[v][i])//如果他们没碰面
  73. 73 {
  74. 74 u=f[u][i],v=f[v][i];//更新数值,继续跳
  75. 75 }
  76. 76 }
  77. 77 return f[u][0];//返回
  78. 78 }//求lca
  79. 79 int main()
  80. 80 {
  81. 81 n=read(),m=read(),s=read();
  82. 82 for(rll i=1;i<n;++i)
  83. 83 {
  84. 84 ll u,v;
  85. 85 u=read(),v=read();
  86. 86 add(u,v);
  87. 87 add(v,u);
  88. 88 }
  89. 89 for(rll i=1;i<=n;++i)
  90. 90 {
  91. 91 lg[i]=lg[i-1]+(1<<lg[i-1]==i);//(1<<lg[i-1])先进行,然后判断是否相等
  92. 92 //如果相等,就说明(1<<lg[i-1])能跳到这里 lg[i]=log2(i)
  93. 93 }
  94. 94 dfs(s,0);
  95. 95 for(rll i=1;i<=m;++i)
  96. 96 {
  97. 97 ll a,b;
  98. 98 a=read(),b=read();
  99. 99 printf("%lld\n",lca(a,b));
  100. 100 }
  101. 101 return 0;
  102. 102 }

LCA——树上倍增的更多相关文章

  1. Codevs 2370 小机房的树 LCA 树上倍增

    题目描述 Description 小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上.有一天,他们想爬到一个节点上去搞基,但是作为两只虫子, ...

  2. HDU 4822 Tri-war(LCA树上倍增)(2013 Asia Regional Changchun)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4822 Problem Description Three countries, Red, Yellow ...

  3. LCA树上倍增

    LCA就是最近公共祖先,比如 节点10和11的LCA就是8,9和3的LCA就是3. 我们这里讲一下用树上倍增来求LCA. 大家都可以写出暴力解法,两个节点依次一步一步往上爬,直到爬到了相同的一个节点. ...

  4. 关于树论【LCA树上倍增算法】

    补了一发LCA,表示这东西表面上好像简单,但是细节真挺多. 我学的是树上倍增,倍增思想很有趣~~(爸爸的爸爸叫奶奶.偶不,爷爷)有一个跟st表非常类似的东西,f[i][j]表示j的第2^i的祖先,就是 ...

  5. 洛谷P3379lca,HDU2586,洛谷P1967货车运输,倍增lca,树上倍增

    倍增lca板子洛谷P3379 #include<cstdio> struct E { int to,next; }e[]; ],anc[][],log2n,deep[],n,m,s,ne; ...

  6. LCA树上倍增求法

    1.LCA LCA就是最近公共祖先(Least common ancestor),x,y的LCA记为z=LCA(x,y),满足z是x,y的公共祖先中深度最大的那一个(即离他们最近的那一个)qwq 2. ...

  7. NOIP2013 货车运输 (最大生成树+树上倍增LCA)

    死磕一道题,中间发现倍增还是掌握的不熟 ,而且深刻理解:SB错误毁一生,憋了近2个小时才调对,不过还好一遍AC省了更多的事,不然我一定会疯掉的... 3287 货车运输 2013年NOIP全国联赛提高 ...

  8. 树上倍增求LCA及例题

    先瞎扯几句 树上倍增的经典应用是求两个节点的LCA 当然它的作用不仅限于求LCA,还可以维护节点的很多信息 求LCA的方法除了倍增之外,还有树链剖分.离线tarjan ,这两种日后再讲(众人:其实是你 ...

  9. 两种lca的求法:树上倍增,tarjan

    第一种:树上倍增 f[x,k]表示x的2^k辈祖先,即x向根结点走2^k步达到的结点. 初始条件:f[x][0]=fa[x] 递推式:f[x][k]=f[ f[x][k-1] ][k-1] 一次bfs ...

随机推荐

  1. 测试覆盖率 之 Cobertura的使用

    什么是代码覆盖率? 代码覆盖率是对整个测试过程中被执行的代码的衡量,它能测量源代码中的哪些语句在测试中被执行,哪些语句尚未被执行. 为什么要测量代码覆盖率? 众所周知,测试可以提高软件版本的质量和可预 ...

  2. 手摸手,带你实现移动端H5瀑布流布局

    移动端瀑布流布局是一种比较流行的网页布局方式,视觉上来看就是一种像瀑布一样垂直落下的排版.每张图片并不是显示的正正方方的,而是有的长有的短,呈现出一种不规则的形状.但是它们的宽度通常都是相同的 因为移 ...

  3. go 语言开发1 环境配置和语言基础

    Go 语言环境配置 windows 环境变量: 设置 GOROOT (安装路径),GOPATH(工程目录) Path 中加入 %GOROOT%/bin 和 %GOPATH%/bin mac 环境变量: ...

  4. ElasticSearch7.3学习(二十四)----相关度评分机制详解

    1.算法介绍 relevance score(相关性分数) 算法,简单来说,就是计算出,一个索引中的文本,与搜索文本,他们之间的关联匹配程度.Elasticsearch使用的是 term freque ...

  5. Oracle中通过逗号分割字符串并转换成多行

    通过逗号对字符串字段进行分割,并返回多行,通过使用regexp_substr()函数实现. SQL示例: select regexp_substr(q.nums, '[^,]+', 1, rownum ...

  6. CIAGAN: Conditional Identity Anonymization Generative Adversarial Networks阅读笔记

    CIAGAN: Conditional Identity Anonymization Generative Adversarial Networks 2020 CVPR 2005.09544.pdf ...

  7. CentOS7 单节点和多节点 HPL测试

    前置工作:安装OpenBLAS; 安装Mpich (可参考首页博客) 官网下载压缩包到/opt目录 cd /opt && wget https://www.netlib.org/ben ...

  8. 原理:C++为什么一般把模板实现放入头文件

    写在前面 本文通过实例分析与讲解,解释了为什么C++一般将模板实现放在头文件中.这主要与C/C++的编译机制以及C++模板的实现原理相关,详情见正文.同时,本文给出了不将模板实现放在头文件中的解决方案 ...

  9. 论文解读(ARVGA)《Learning Graph Embedding with Adversarial Training Methods》

    论文信息 论文标题:Learning Graph Embedding with Adversarial Training Methods论文作者:Shirui Pan, Ruiqi Hu, Sai-f ...

  10. 使用kubeseal加密和管理k8s集群的secret

    使用kubeseal加密和管理k8s集群的secret 在k8s的管理过程中,像secret这种资源并不好维护,kubeseal提供了一种相对简单的方式来对原始secret资源进行加密,并通过控制器进 ...