什么是LCA?

话不多说,同志们先来康康LCA是什么东西.(逃

LCA“光辉”是印度斯坦航空公司(HAL)为满足印度空军需要研制的单座单发轻型全天候超音速战斗攻击机,主要任务是争夺制空权、近距支援,是印度自行研制的第一种高性能战斗机。------摘自百度百科

当然,同志们认识的LCA可不是那个研制了三十年的烂玩意.

在信息学竞赛中,LCA指的是"Lowest Common Ancestors",即"最近公共祖先".算法目的是在一颗有根树中,求出结点\(x\)和\(y\)最近的公共祖先.

那么什么是最近的公共祖先呢?斯大林格勒的拖拉机工人们给出了这样一幅图:

首先我们得理解祖先的概念.对与任意一个树上的结点,与它有亲缘关系,且深度比它小的结点都是它的祖先.

在这幅图中,3号结点的祖先为2和1,6号结点的祖先为5和1,所以它们有公共的祖先1,所以说3和6的LCA为1.

再举一个例子,3结点的祖先为2和1,4号结点的祖先为2和1,它们有公共祖先2和1,但是2是距离它们最近的祖先,所以说3和4的LCA为2.

怎样建设求出LCA?

求LCA一般可用到倍增,Tarjan(不是用于缩点那个Tarjan)这两种算法,在这里一一讲解.

倍增版LCA

主体思想(请勿联想到某金姓领导人)

倍增是一种二进制拆分的思想,其已广泛应用于ST表,求解LCA等算法,为我国生产力的发展,推进共产主义的早日实现做出了巨大贡献.

实现方式

类比ST表的实现方式,同志们可以设\(path[i][j]\)为结点i向上跳\(2^j\)后到达的结点.显然,\(path[i][0]\)就是\(i\)结点的父亲.

那么如何进行二进制拆分呢?显然,\(path[i][j-1]\)向上再跳\(2^{j-1}\)次后到达的结点就是\(path[i][j]\).

于是同志们可以这样预处理:

path[i][j]=path[f[i][j-1]][j-1];

意为:\(i\)号结点向上跳\(2^j\)个长度到达的结点,等于\(i\)号结点向上跳\(2^{j-1}\)个结点到达的结点再向上跳\(2^{j-1}\)个结点.

然后将两个结点提至同一深度,不断地向上跳即可求出它们的LCA.

建设求出LCA的具体步骤

  1. 进行预处理.

  2. 把结点x和y调整至同一高度.

  3. 将结点x和y同时向上调整,保持深度一致且二点不相会.具体地说,就是将\(x\)和\(y\)以此向上走\(k\)=\(2^{logn}\),...,\(2^1\),\(2^0\)步,如果\(path[x][k]\)!=\(path[y][k]\)(即两点还未相会),就令\(x\)=\(path[x][k]\),\(y\)=\(path[y][k]\).

  4. 这时\(x\)与\(y\)只差一步就相会了,返回\(path[x][0]\),即\(x\)的父亲,即为\(x\)和\(y\)的LCA.

该算法的时间复杂度为\(O(log2(Depth))\)

模板题

代码:


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<vector>using namespace std;struct edge
{int next,to;
}e[1000010];int n,m,s,size;
int head[500010],depth[500010],path[500010][51];void EdgeAdd(int,int);
int LCA(int,int);
void DFS(int,int);int main()
{memset(head,-1,sizeof(head));scanf("%d%d%d",&n,&m,&s);for(int _=1;_<=n-1;_++){int father,son;scanf("%d%d",&father,&son);EdgeAdd(father,son);EdgeAdd(son,father);}DFS(s,0);for(int _=1;_<=m;_++){int a,b;scanf("%d%d",&a,&b);printf("%d\n",LCA(a,b));}
return 0;
}void EdgeAdd(int from,int to)
{e[++size].to=to;e[size].next=head[from];head[from]=size;
}void DFS(int from,int father)
{depth[from]=depth[father]+1;path[from][0]=father;for(int _=1;(1<<_)<=depth[from];_++){path[from][_]=path[path[from][_-1]][_-1];}for(int _=head[from];_!=-1;_=e[_].next){int to=e[_].to;if(to!=father){DFS(to,from);}}
}int LCA(int a,int b)
{if(depth[a]>depth[b]){swap(a,b);}for(int _=20;_>=0;_--){if(depth[a]<=depth[b]-(1<<_)){b=path[b][_];}}if(a==b){return a;}for(int _=20;_>=0;_--){if(path[a][_]==path[b][_]){continue;}else{a=path[a][_];b=path[b][_];}}
return path[a][0];
}

Tarjan版LCA

Tarjan版的LCA是离线的,而上文介绍的倍增版LCA是在线的,所以说如果不是直接输出LCA的话,需要一个数组来记录它.

主体思想

从根结点遍历这棵树,遍历到每个结点并使用并查集记录父子关系.

实现方式

用并查集记录父子关系,将遍历过的点合并为一颗树.

若两个结点\(x\),\(y\)分别位于结点\(a\)的左右子树中,那么结点\(a\)就为\(x\)与\(y\)的LCA.

考虑到该结点本身就是自己的LCA的情况,做出如下修改:

若\(a\)是\(x\)和\(y\)的祖先之一,且\(x\)和\(y\)分别在\(a\)的左右子树中,那么\(a\)便是\(x\)和\(y\)的LCA.

这个定理便是Tarjan版LCA的实现基础.

具体步骤

当遍历到一个结点\(x\)时,有以下步骤:

  1. 把这个结点标记为已访问.

  2. 遍历这个结点的子结点\(y\),并在回溯时用并查集合并\(x\)和\(y\).

  3. 遍历与当前结点有查询关系的结点\(z\),如果\(z\)已被访问,则它们的LCA就为\(find(z)\).

需要同志们注意的是,存查询关系的时候是要双向存储的.

该算法的时间复杂度为\(O(n+m)\)

Tarjan版的LCA很少用到,但为了方便理解,这里引用了参考文献2里的代码,望原博主不要介意.

代码:


#include<bits/stdc++.h>
using namespace std;
int n,k,q,v[100000];
map<pair<int,int>,int> ans;//存答案
int t[100000][10],top[100000];//存储查询关系
struct node{int l,r;
};
node s[100000];
/*并查集*/
int fa[100000];
void reset(){for (int i=1;i<=n;i++){fa[i]=i;}
}
int getfa(int x){return fa[x]==x?x:getfa(fa[x]);
}
void marge(int x,int y){fa[getfa(y)]=getfa(x);
}
/*------*/
void tarjan(int x){v[x]=1;//标记已访问node p=s[x];//获取当前结点结构体if (p.l!=-1){tarjan(p.l);marge(x,p.l);}if (p.r!=-1){tarjan(p.r);marge(x,p.r);}//分别对l和r结点进行操作for (int i=1;i<=top[x];i++){if (v[t[x][i]]){cout<<getfa(t[x][i])<<endl;}//输出}
}
int main(){cin>>n>>q;for (int i=1;i<=n;i++){cin>>s[i].l>>s[i].r;}for (int i=1;i<=q;i++){int a,b;cin>>a>>b;t[a][++top[a]]=b;//存储查询关系t[b][++top[b]]=a;}reset();//初始化并查集tarjan(1);//tarjan 求 LCA
}

参考文献

参考文献1

参考文献2

参考文献3

转载于:https://www.cnblogs.com/Lemir3/p/11112663.html

斯大林格勒拖拉机厂LCA项目研制成功相关推荐

  1. 永磁直驱风力发电机结构图_风机越来越大,国内首台10兆瓦海上风力发电机研制成功...

    记者 | 席菁华 编辑 | 国内首台10兆瓦海上永磁直驱风力发电机研制成功.这台发电机将发往福建省,安装在兴化湾海上风电场. 据东方电气股份有限公司(下称东方电气,600875.SH)官网显示,8月2 ...

  2. 永磁直驱风力发电机结构图_国内首台10MW海上永磁直驱风力发电机研制成功

    2019首届新能源产业投融资论坛 2019年10月25日 周老师:15712959596 8月21日,具有完全自主知识产权.国内首台10MW海上永磁直驱风力发电机在东方电气集团东方电机有限公司研制成功 ...

  3. 中国研制成功人工智能探地雷达 可为地下空间做“体检”

    图为35所科技委主任焦晓亮(左1)在广州举行的展会上介绍"鹰眼"探地雷达产品. 35所/供图 摄 中新网北京1月17电(记者 孙自法)一座城市不仅有地上的高楼林立.车水马龙,还有看 ...

  4. 国内首个基于Windows操作系统的GPU高性能计算集群研制成功

    国内首个基于Windows操作系统的GPU高性能计算集群研制成功 一个普通的实验室里,在一台普通的台式机上发出一条集群工作的指令,通过一个小小的交换机,三台计算机同时进行演算,一个蒙特卡洛的计算问题在 ...

  5. 中国首台千万亿次超级计算机叫什么,中国首台千万亿次超级计算机系统研制成功...

    中新社长沙十月二十九日电 (肖前辉 王握文)国防科学技术大学二十九日对外公布,中国首台千万亿次超级计算机系统--"天河一号"在该校研制成功,中国成为继美国之后世界上第二个能够研制千 ...

  6. 3D打印防护面罩在京研制成功;马斯克在家举办Hackthon;手机可拍出单反照片

    行 业 要 闻 Industry   News ▲▲▲ 0 1 3D打印/复印防护面罩在京研制成功 近日,北京化工大学教授研制出具有微纳滤膜清新空气微正压输入与呼出气体在线消毒排放功能的"新 ...

  7. 银河6超级计算机,1997年6月22日 银河——III巨型计算机研制成功

    1997年6月22日 银河--III巨型计算机研制成功 1993年6月22日,标志我国高科技领域又一重大突破的"银河全数字仿真·II"计算机研制成功并通过国家鉴定. 专家认为,这一 ...

  8. 为什么说新型冠状病毒疫苗会研制成功?

    撰文:李大鹏 | Duke University 原载:返朴(fanpu2019) 量子位QbitAI 授权转载 世界上第一支疫苗诞生于18世纪末的英国.英国医生爱德华·詹納 (Edward Jenn ...

  9. springboot:SpringBoot项目启动成功,但无法访问且提示404

    当SpringBoot项目启动成功后,访问controller下的接口却发现无法访问,且提示status=404 我的项目中controller下只有一个测试接口,接口名为MainController ...

最新文章

  1. BCVP开发者社区2022专属周边第一弹
  2. 球体表面积原来还可以这么求!
  3. Java Statement PK PrepareStatement
  4. 电大计算机考试题目excel,电大计算机考试复习题EXCEL部分
  5. UBI文件系统和镜像的制作及分区挂载说明
  6. 哈工大刘挺:如何训练一个更强大的中文语言模型?
  7. python统计中文字数_Python实现统计文本文件字数的方法
  8. 软件测试之测试计划案例
  9. autojs识别二维码
  10. Tomcat中使用cookie
  11. 【只推荐一位】木东居士,数据挖掘的大神!
  12. 关于VC++调试项 Multi-threaded Debug DLL的问题。
  13. SpringBoot:yaml配置及语法、yml数据读取、多环境开发控制
  14. 小程序中子组件父/组件互相传值以及互相接收
  15. 程序员哥哥,你有一枚女朋友请查收。
  16. HCI - ImageJ软件的简介
  17. 在html表格中进行计算,如何在word表格中实现计算功能?超级实用,值得细细品味...
  18. Hello Jexus
  19. latex编译中遇到的错误
  20. android控制台灯

热门文章

  1. 微信支付开发(2) 扫码支付模式一
  2. POJ-3264-Balanced Lineup
  3. JSTL与JAVA数据交互 pagecontext
  4. 跟我一起学jQuery——第二集(未完待续..)
  5. NetCat瑞士军刀渗透工具使用详解
  6. Linux基本操作(6)——Linux 重写rm 命令 实现回收站功能
  7. 当开发帆软插件得时候如果安装插件遇到 ***插件版本过低 请安装高版本插件
  8. 推荐的字符与字符串处理方式
  9. visio中公式太小_五金冲压模具中的凹模有哪些注意事项,值得一看
  10. pythonsuper继承规则,深入理解Python中的super()方法