本文原发布时间:\(\texttt{2022-05-21 14:11:52}\)

简介

最经公共祖先 \(\operatorname{LCA}(a,b)=c\),指的是在一棵树上节点 \(a\) 与 \(b\) 之间离两个点最近的一个点 \(c\),使得 \(c\) 是它们的祖先。

比如说,下面这棵树:

那么 \(\operatorname{LCA}(6,7)=2\),注意,不是 \(1\)。

例题(P3379 【模板】最近公共祖先

题目描述

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

输入格式

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

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

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

输出格式

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

提示说明

对于 \(30\%\) 的数据,\(N\leq 10\),\(M\leq 10\)。

对于 \(70\%\) 的数据,\(N\leq 10000\),\(M\leq 10000\)。

对于 \(100\%\) 的数据,\(N\leq 500000\),\(M\leq 500000\)。

2021/10/4 数据更新 @fstqwq:应要求加了两组数据卡掉了暴力跳。

求法

0.暴力法

思路很简单。

首先预处理深度,询问时先统一深度,然后逐级上推即可。

最差时间复杂度 \(O(NM)\) 只能通过 \(70\%\) 的数据。

1.倍增法

倍增法本质上是对暴力法的优化。

首先,我们不必一个一个跳,可以指数级跳跃,效率大幅度提升。

具体如下:

首先,预处理节点 \(i\) 的第 \(2^{j}\) 级祖先 \(\operatorname{FA}[i][j]\),当然,还有深度。

询问时,先调整深度,然后倍增跳跃即可。

时间复杂度 \(O(N+M\log N)\),可以通过所有数据。

代码

我用的是链式前向星存图。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. struct edge{
  4. int nxt,to,w;
  5. } tree[500005*2];
  6. int head[500005],ec;
  7. void add(int from,int to,int weight){
  8. tree[++ec].nxt=head[from];
  9. tree[ec].to=to;
  10. tree[ec].w=weight;
  11. head[from]=ec;
  12. }
  13. int fa[500005][55],deep[500005],lg2[500005];
  14. inline void initlog(int n){
  15. lg2[1]=0;
  16. lg2[2]=1;
  17. for(int i=3;i<=n;i++){
  18. lg2[i]=lg2[i>>1]+1;
  19. }
  20. }
  21. void dfs(int now,int parent){
  22. fa[now][0]=parent;
  23. deep[now]=deep[parent]+1;
  24. for(int i=1;(1<<i)<=deep[now];i++){
  25. fa[now][i]=fa[fa[now][i-1]][i-1];
  26. }
  27. for(int i=head[now];i;i=tree[i].nxt){
  28. if(tree[i].to != parent){
  29. dfs(tree[i].to,now);
  30. }
  31. }
  32. }
  33. int lca(int x,int y){
  34. if(deep[x]<deep[y]){
  35. swap(x,y);
  36. }
  37. while(deep[x]!=deep[y]){
  38. x=fa[x][lg2[deep[x]-deep[y]-1]];
  39. }
  40. if(x==y){
  41. return x;
  42. }
  43. for(int k=lg2[deep[x]];k>=0;k--){
  44. if(fa[x][k]!=fa[y][k]){
  45. x=fa[x][k];
  46. y=fa[y][k];
  47. }
  48. }
  49. return fa[x][0];
  50. }
  51. int n,m,s;
  52. int main(){
  53. cin>>n>>m>>s;
  54. for(int i=1,u,v;i<=n-1;i++){
  55. cin>>u>>v;
  56. add(u,v,114514);
  57. add(v,u,1919810);
  58. }
  59. initlog(n);
  60. dfs(s,0);
  61. for(int i=1,arcka,akioi;i<=m;i++){
  62. cin>>arcka>>akioi;
  63. cout<<lca(arcka,akioi)<<endl;
  64. }
  65. return 0;
  66. }

AC in Luogu

Tarjan

埋坑。

例题

(原创)Game2:简单树上问题

题目描述

给你一个 \(N\) 顶点的树,有 \(M\) 个询问,每次询问给出两个节点 \(u,v\) 求这两个点之间的最短距离。

输入格式

第一行为 \(N,M\)

接下来 \(N-1\) 行,每行三个整数 \(u,v,w\),表示一条从 \(u\) 到 \(v\) 的边,请忽略 \(w\)。

然后 \(M\) 行,每行两个整数 \(u,v\)。

输出格式

对于每一个询问,输出一个整数,表示答案。

提示

\(N,M = 100000,1 \le u,v,w \le N\)

时间限制 \(600\operatorname{ms}\),空间限制 \(50\operatorname{MB}\)。

造数据程序

  1. from cyaron import *
  2. n = 100000
  3. m=n
  4. for i in range(1, 25):
  5. test = IO(file_prefix="data/test",data_id=i)
  6. test.input_writeln(n,m)
  7. graph=Graph.tree(n)
  8. test.input_writeln(graph)
  9. for i in range(m):
  10. test.input_writeln(randint(1,n),randint(1,n))
  11. test.output_gen("C:/Users/stu01/Documents/hh.exe")

思路

这道题可以直接用倍增LCA解决,因为:

\[\operatorname{Dis}(u,v)=\operatorname{Deep}(u)+\operatorname{Deep}(v)-2 \times \operatorname{Deep}(\operatorname{LCA}(u,v))
\]

代码

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. struct edge{
  4. int nxt,to,w;
  5. } tree[500005*2];
  6. int head[500005],ec;
  7. void add(int from,int to,int weight){
  8. tree[++ec].nxt=head[from];
  9. tree[ec].to=to;
  10. tree[ec].w=weight;
  11. head[from]=ec;
  12. }
  13. int fa[500005][55],deep[500005],lg2[500005];
  14. inline void initlog(int n){
  15. lg2[1]=0;
  16. lg2[2]=1;
  17. for(int i=3;i<=n;i++){
  18. lg2[i]=lg2[i>>1]+1;
  19. }
  20. }
  21. void dfs(int now,int parent){
  22. fa[now][0]=parent;
  23. deep[now]=deep[parent]+1;
  24. for(int i=1;(1<<i)<=deep[now];i++){
  25. fa[now][i]=fa[fa[now][i-1]][i-1];
  26. }
  27. for(int i=head[now];i;i=tree[i].nxt){
  28. if(tree[i].to != parent){
  29. dfs(tree[i].to,now);
  30. }
  31. }
  32. }
  33. int lca(int x,int y){
  34. if(deep[x]<deep[y]){
  35. swap(x,y);
  36. }
  37. while(deep[x]!=deep[y]){
  38. x=fa[x][lg2[deep[x]-deep[y]-1]];
  39. }
  40. if(x==y){
  41. return x;
  42. }
  43. for(int k=lg2[deep[x]];k>=0;k--){
  44. if(fa[x][k]!=fa[y][k]){
  45. x=fa[x][k];
  46. y=fa[y][k];
  47. }
  48. }
  49. return fa[x][0];
  50. }
  51. int n,m,s=1;
  52. int main(){
  53. cin>>n>>m;
  54. for(int i=1,u,v,w;i<=n-1;i++){
  55. cin>>u>>v>>w;
  56. add(u,v,114514);
  57. add(v,u,1919810);
  58. }
  59. initlog(n);
  60. dfs(s,0);
  61. for(int i=1,arcka,akioi;i<=m;i++){
  62. cin>>arcka>>akioi;
  63. cout<<deep[arcka]+deep[akioi]-2*deep[lca(arcka,akioi)]<<endl;
  64. }
  65. return 0;
  66. }

P4281 [AHOI2008]紧急集合 / 聚会 | LibreOJ10136. 「一本通 4.4 练习 3」聚会

简要题意

对于一个有根树,有 \(n\) 个节点,有 \(m\) 个询问,每次询问提供三个节点 \(x,y,z\),求一点 \(p\),使得 \(c=\operatorname{Dis}(x,p)+\operatorname{Dis}(y,p)+\operatorname{Dis}(z,p)\) 最小,输出 \(p\) 和 \(c\)。(\(\operatorname{Dis}(u,v)\) 表示 \(u\) 到 \(v\) 的最短路径长度)

对于 \(100\%\) 的数据,\(1\leq x,y,z\leq n\leq 5\times10^5\),\(1\leq m\leq 5\times 10^5\)。

思路

首先先来口胡一个结论,不重复的节点才是最优解。

然后就是算长度了,由于我们是三个点走到一个,那么就是这个:

\[\begin{aligned}
\operatorname{Dis}(x,y,z)=
\operatorname{Deep}(x)+
\operatorname{Deep}(y)+
\operatorname{Deep}(z)-\\
\operatorname{Deep}(\operatorname{LCA}(x,y))-
\operatorname{Deep}(\operatorname{LCA}(x,z))-
\operatorname{Deep}(\operatorname{LCA}(y,z))
\end{aligned}
\]

自己Hand推

时间复杂度 \(O(n+m\log n)\)。注意,本题需要卡常。

代码

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. struct edge{
  4. int nxt,to,w;
  5. } tree[500005*2];
  6. int head[500005],ec;
  7. void add(int from,int to,int weight){
  8. tree[++ec].nxt=head[from];
  9. tree[ec].to=to;
  10. tree[ec].w=weight;
  11. head[from]=ec;
  12. }
  13. int fa[500005][55],deep[500005],lg2[500005];
  14. inline void initlog(int n){
  15. lg2[1]=0;
  16. lg2[2]=1;
  17. for(int i=3;i<=n;i++){
  18. lg2[i]=lg2[i>>1]+1;
  19. }
  20. }
  21. void dfs(int now,int parent){
  22. fa[now][0]=parent;
  23. deep[now]=deep[parent]+1;
  24. for(int i=1;(1<<i)<=deep[now];i++){
  25. fa[now][i]=fa[fa[now][i-1]][i-1];
  26. }
  27. for(int i=head[now];i;i=tree[i].nxt){
  28. if(tree[i].to != parent){
  29. dfs(tree[i].to,now);
  30. }
  31. }
  32. }
  33. int lca(int x,int y){
  34. if(deep[x]<deep[y]){
  35. swap(x,y);
  36. }
  37. while(deep[x]!=deep[y]){
  38. x=fa[x][lg2[deep[x]-deep[y]-1]];
  39. }
  40. if(x==y){
  41. return x;
  42. }
  43. for(int k=lg2[deep[x]];k>=0;k--){
  44. if(fa[x][k]!=fa[y][k]){
  45. x=fa[x][k];
  46. y=fa[y][k];
  47. }
  48. }
  49. return fa[x][0];
  50. }
  51. int n,m;
  52. inline int cd(int a,int b,int LCA){
  53. return deep[a]+deep[b]-2*deep[LCA];
  54. }
  55. int main(){
  56. ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  57. cin>>n>>m;
  58. for(int i=1,u,v;i<=n-1;i++){
  59. cin>>u>>v;
  60. add(u,v,114514);
  61. add(v,u,1919810);
  62. }
  63. initlog(n);
  64. dfs(1,0);
  65. while(m--){
  66. int x,y,z;
  67. cin>>x>>y>>z;
  68. int l1=lca(x,y),l2=lca(x,z),l3=lca(y,z),ll=0;
  69. if(l1==l2)ll=l3;
  70. else if(l1==l3)ll=l2;
  71. else ll=l1;
  72. cout<<ll<<' ';
  73. cout<<(deep[x]+deep[y]+deep[z]-deep[l1]-deep[l2]-deep[l3])<<'\n';
  74. }
  75. return 0;
  76. }

AC in LibreOJ

AC in Luogu

LCA学习笔记(原洛谷文章)的更多相关文章

  1. 倍增求LCA学习笔记(洛谷 P3379 【模板】最近公共祖先(LCA))

    倍增求\(LCA\) 倍增基础 从字面意思理解,倍增就是"成倍增长". 一般地,此处的增长并非线性地翻倍,而是在预处理时处理长度为\(2^n(n\in \mathbb{N}^+)\ ...

  2. 2-SAT问题学习笔记+例题[洛谷P4792]

    一个不错的2-SAT文章:传送门 问题初入 什么是2-SAT SAT是适定性(Satisfiability)问题的简称 .一般形式为k-适定性问题,简称 k-SAT. 首先,把「2」和「SAT」拆开. ...

  3. dp凸优化/wqs二分学习笔记(洛谷4383 [八省联考2018]林克卡特树lct)

    qwq 安利一个凸优化讲的比较好的博客 https://www.cnblogs.com/Gloid/p/9433783.html 但是他的暴力部分略微有点问题 qwq 我还是详细的讲一下这个题+这个知 ...

  4. 2.《Spring学习笔记-MVC》系列文章,讲解返回json数据的文章共有3篇,分别为:

    转自:https://www.cnblogs.com/ssslinppp/p/4528892.html 个人认为,使用@ResponseBody方式来实现json数据的返回比较方便,推荐使用. 摘要 ...

  5. JavaEE精英进阶课学习笔记《博学谷》

    JavaEE精英进阶课学习笔记<博学谷> 第1章 亿可控系统分析与设计 学习目标 了解物联网应用领域及发展现状 能够说出亿可控的核心功能 能够画出亿可控的系统架构图 能够完成亿可控环境的准 ...

  6. AngularJs学习笔记--Guide教程系列文章索引

    在很久很久以前,一位前辈向我推荐AngularJs.但当时我没有好好学习,仅仅是讲文档浏览了一次.后来觉醒了……于是下定决心好好理解这系列的文档,并意译出来(英文水平不足……不能说是翻译,有些实在是看 ...

  7. 【算法学习】【洛谷】树链剖分 & P3384 【模板】树链剖分 P2146 软件包管理器

    刚学的好玩算法,AC2题,非常开心. 其实很早就有教过,以前以为很难就没有学,现在发现其实很简单也很有用. 更重要的是我很好调试,两题都是几乎一遍过的. 介绍树链剖分前,先确保已经学会以下基本技巧: ...

  8. 3.《Spring学习笔记-MVC》系列文章,讲解返回json数据的文章共有3篇,分别为:

    转自:https://www.cnblogs.com/ssslinppp/p/4528892.html 概述 在文章:<[Spring学习笔记-MVC-3]SpringMVC返回Json数据-方 ...

  9. 1.《Spring学习笔记-MVC》系列文章,讲解返回json数据的文章共有3篇,分别为:

    转自:https://www.cnblogs.com/ssslinppp/p/4528892.html [Spring学习笔记-MVC-3]SpringMVC返回Json数据-方式1:http://w ...

  10. 【算法学习】【洛谷】cdq分治 & P3810 三维偏序

    cdq是何许人也?请参看这篇:https://wenku.baidu.com/view/3b913556fd0a79563d1e7245.html. 在这篇论文中,cdq提出了对修改/询问型问题(Mo ...

随机推荐

  1. Linux进程间通信(一)

    进程间通信 概念:进程是一个独立的资源分配单位,不同进程之间有关联,不能在一个进程中直接访问另一个进程的资源. 进程和进程之间的资源是相互独立的,一个进程不能直接访问另外一个进程的资源,但是进程和进程 ...

  2. HDFS基础学习

    HDFS简介 HDFS即Hadoop Distributed File System,是一个分布式文件系统,用于存储海量数据.一个HDFS集群由一个NameNode和多个DataNode组成. HDF ...

  3. 题解 P3395 路障

    前言 没想到这是\(\sf {tgD1T1}\)难度-- 题目大意 有一个\(n\times n\) 的棋盘,有\(2n-2\) 个路障,在第\(i\) 秒会在\((x_i,y_i)\) 放置路障.求 ...

  4. vue-axios增加操作

    <template> <div class="Insert"> <label for="name">名称:</labe ...

  5. CLR、CLS、CTS概述

    在学习.NET的过程中,都会不可避免地接触到这三个概念,那么这三个东西是什么以及它们之间的关系是怎样的呢?任何编程语言,如果想要在.NET CLR上执行,就必需提供一个编译器,将此语言的程序编译成.N ...

  6. 2022-11-12 Acwing每日一题

    本系列所有题目均为Acwing课的内容,发表博客既是为了学习总结,加深自己的印象,同时也是为了以后回过头来看时,不会感叹虚度光阴罢了,因此如果出现错误,欢迎大家能够指出错误,我会认真改正的.同时也希望 ...

  7. Java自定义排序

    实现Comparator接口 实现该接口需要重写compare()方法 Arrays.sort(students, new Comparator<Student>() { @Overrid ...

  8. C++两种方法改变输出颜色

    方法一: 使用 SetConsoleTextAttribute    需要引入 #include "windows.h"    SetConsoleTextAttribute(Ge ...

  9. SqlServer 联合Update

    --1.创建一个备份表: select * into Users_Bak from Users --2.依据某个表进行更新: update Users_Bak set Users_Bak.Mobile ...

  10. 关于linux建立u盘legacy启动方式引导

    前言 我一直在用linux,但是我在linux制作pe启动盘无法实现,windows有很多制作pe启动盘的软件,如大白菜,u深度什么的,但是linux没有对应的软件,所以我想写一个类似的工具,那么就有 ...