树论讲解——最近公共祖先(lca)
最近公共祖先?!
有人肯定要问:什么是最近公共祖先???!!
好那我们现在就来说说什么是最近公共祖先吧!
最近公共祖先有一个好听的名字叫——lca
这是一种算法,这个算法基于并查集和深度优先搜索。算法从根开始,对每一棵子树进行深度优先搜索,访问根时,将创建由根结点构建的集合,然后对以他的孩子结点为根的子树进行搜索,使对于 u, v 属于其某一棵子树的 LCA 询问完成。这时将其所有子树结点与根结点合并为一个集合。 对于属于这个集合的结点 u, v 其 LCA 必定是根结点。
对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。
怎么求两个已知点的LCA呢·?
有一个比较暴力的想法:先将这两个点的路径上的所经过的所有点,然后再从根节点向下找第一个分叉的点,这个点就是这两个点的最近公共祖先。
还有一个想法:先将两个深度不同的点转化成深度相同的点,然后再将这两个点一起向上跳,直到找到同一个点。
§ one。倍增法
何为倍增法?
倍增法就是我们先把深度不同的两个点转化成深度相同的点。然后再对这两个点同时倍增。
这种做法我们先用一个数组fa[x]【y】数组来存第x个节点的2^y的父亲节点。
这样我们就能在o(lg n)的时间内查询任意一个点的lca。
所以我们还是采用上面所述的那种做法,现将深度不同的两个点转化成深度相同的两个点。
然后再对两个点同时进行倍增。
好那我们下面来求一求给定两点:x,y的最近公共祖先吧!
代码1:
#include<vector>
#include<stdio.h>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 500001
#define maxn 123456
using namespace std;
vector<int>vec[N];
],deep[N],m,root;
void dfs(int x)
{
deep[x]=deep[fa[x][]]+;
;fa[x][i];i++)
fa[x][i+]=fa[fa[x][i]][i];
;i<vec[x].size();i++)
{
if(!deep[vec[x][i]])
{
fa[vec[x][i]][]=x;
dfs(vec[x][i]);
}
}
}
int lca(int x,int y)
{
if(deep[x]>deep[y])
swap(x,y);//省下后面进行分类讨论,比较方便
;i>=;i--)
{
if(deep[fa[y][i]]>=deep[x])
y=fa[y][i];//让一个点进行倍增,直到这两个点的深度相同
}
if(x==y) return x;//判断两个点在一条链上的情况
;i>=;i--)
{
if(fa[x][i]!=fa[y][i])
{
y=fa[y][i];
x=fa[x][i];
}
}
];//这样两点的父亲就是他们的最近公共祖先
}
int main()
{
scanf("%d%d%d",&n,&m,&root);
;i<n;i++)
{
scanf("%d%d",&x,&y);
vec[x].push_back(y);
vec[y].push_back(x);
}
deep[root]=;
dfs(root);
;i<=m;i++)
{
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
;
}
§ two。树剖法。
看到这个算法,肯定有想问树剖法是个什么鬼?
树抛嘛,顾名思义肯定是将一棵树进行剖分。
树链抛分的核心是划分一棵树的重边和轻边。
我们把一个节点所拥有的子节点的个数记为size
对于一棵树来说,一个节点u和他的父节点v之间一定只有一条重边。这条重边连向她的儿子中size值最大的节点。
这样一棵树的所有重边就组成了一条重链。
对于节点x我们记录x的重边的顶点top x.
加入一个节点到另一条节点有轻边,那这条轻边满足:2*size u<size V
因此,根到一棵树的节点上最多有long n 条轻边
现在我们要用树链剖分来求两节点x,y的lca
首先我们要先判断两个节点的top那个大
我们把top值小的节点改成fa[top[x]]
重复上述两个过程直到top[x]=top[y]
最后将上述两个节点中深度较小的值作为这两点的lca。
下面给出本人代码
#include<vector>
#include<stdio.h>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 500001
#define maxn 123456
using namespace std;
vector<int>vec[N];
int n,m,root,x,y,fa[N],deep[N],size[N],top[N];
int lca(int x,int y)
{
for( ;top[x]!=top[y];)
{
if(deep[top[x]]<deep[top[y]])
swap(x,y);
x=fa[x];
}
if(deep[x]>deep[y])
swap(x,y);
return x;
}
void dfs(int x)
{
size[x]=;
deep[x]=deep[fa[x]]+;
;i<vec[x].size();i++)
{
if(fa[x]!=vec[x][i])
{
fa[vec[x][i]]=x;
dfs(vec[x][i]);
size[x]+=size[vec[x][i]];
}
}
}
void dfs1(int x)
{
;
if(!top[x]) top[x]=x;
;i<vec[x].size();i++)
if(vec[x][i]!=fa[x]&&size[vec[x][i]]>size[t])
t=vec[x][i];
if(t)
{
top[t]=top[x];
dfs1(t);
}
;i<vec[x].size();i++)
if(vec[x][i]!=fa[x]&&vec[x][i]!=t)
dfs1(vec[x][i]);
}
int main()
{ scanf("%d%d%d",&n,&m,&root);
;i<n;i++)
{
scanf("%d%d",&x,&y);
vec[x].push_back(y);
vec[y].push_back(x);
}
dfs(root);
dfs1(root);
;i<=m;i++)
{
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
;
}
§ three。并查集
对于一棵树的每个节点父亲的查询问题,我们可以采用并查集的方法。
我们先用一个数组fa[x]来储存x的父亲节点,每个点与他的父亲节点在一个并查集里。
如果一个点满足:fa[x]=x,则说明x是这棵树的根节点。
我们在查询两个节点是否在一个并查集中时,只要查询这两个节点的所在子树根节点是否相同即可。
在合并两个字数的集合时,我们只要讲fa[root[x]]变成root[x]即可。
有没有感觉这个方法教前面的几个方法来说要简单很多?
好,既然这么简单,我们就直接上代码吧!
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 10000
using namespace std;
int n,m,t,fa[N],x,y;
int find(int x)
{
return fa[x]==x?x:fa[x]=find(x);
}
int main()
{
scanf("%d%d",&n,&m);
;i<=n;i++)
fa[i]=i;
;i<=m;i++)
{
scanf("%d%d%d",&t,&x,&y);
) fa[find(x)]=find(y);
else
{
if(find(x)==find(y)) printf("YES");
else printf("NO");
}
}
;
}
§ four。tarjian法(简称塔尖)
与之前的树抛和倍增法不同,tarjian算是一种离线算法。
我们需要将米一组询问用vec储存下来,将其挂在改组询问询问的两个节点上。
之后遍历整棵树。在访问一个节点x时,我们设置这个节点的fa【x】=x;只有在询问完时,我们将这个点的fa【x】设置城dad【x】。
之后我们在询问一个节点时,枚举过于这个点的所有询问。若果询问中的另一个节点已经被访问过,那么这两个点的lca就是已经被访问过的这个点。
代码
#include<vector>
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 500001
using namespace std;
vector<int>vec[N],que[N];
int n,m,qx[N],qy[N],x,y,root,fa[N],dad[N],ans[N];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void dfs(int x)
{
fa[x]=x;
;i<vec[x].size();i++)
if(vec[x][i]!=dad[x])
dad[vec[x][i]]=x,dfs(vec[x][i]);
;i<que[x].size();i++)
if(dad[y=qx[que[x][i]]^qy[que[x][i]]^x])
ans[que[x][i]]=find(y);
fa[x]=dad[x];
}
int main()
{
scanf("%d%d%d",&n,&m,&root);
;i<n;i++)
{
scanf("%d%d",&x,&y);
vec[x].push_back(y);
vec[y].push_back(x);
}
;i<=m;i++)
{
scanf("%d%d",&qx[i],&qy[i]);
que[qx[i]].push_back(i);
que[qy[i]].push_back(i);
}
dfs(root);
;i<=m;i++)
printf("%d\n",ans[i]);
;
}
树论讲解——最近公共祖先(lca)的更多相关文章
- POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)
POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...
- POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)
POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...
- [模板] 最近公共祖先/lca
简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...
- 【lhyaaa】最近公共祖先LCA——倍增!!!
高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...
- Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)
Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...
- 【Leetcode】查找二叉树中任意结点的最近公共祖先(LCA问题)
寻找最近公共祖先,示例如下: 1 / \ 2 3 / \ / \ 4 5 6 7 / \ ...
- 最近公共祖先LCA(Tarjan算法)的思考和算法实现
LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了f ...
- 最近公共祖先(LCA)的三种求解方法
转载来自:https://blog.andrewei.info/2015/10/08/e6-9c-80-e8-bf-91-e5-85-ac-e5-85-b1-e7-a5-96-e5-85-88lca- ...
- 最近公共祖先(LCA)模板
以下转自:https://www.cnblogs.com/JVxie/p/4854719.html 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖 ...
随机推荐
- JVM学习四:JVM之类加载器之初始化分析
在经过了前面的加载 和 连接分析之后,这一节我们进入重要的初始化分析过程: 一.认识初始化 初始化:这个似乎与上面的初始化为默认值有点矛盾,我们再看一遍:为累的静态变量赋予正确的初始值,上面是赋予默 ...
- 部署维护docker环境
其实前面已经用salt,安装部署了docker应用环境了,过程中还是遇到了不少问题,所以这里再相对仔细的记录一下,docker手机安装过程应注意的事情 安装过程部分参考了刘天斯大师文档部署 1,安装环 ...
- windows安装zookeeper和kafka,flume
一.安装JDK 过程比较简单,这里不做说明. 最后打开cmd输入如下内容,表示安装成功 二.安装zooeleeper 下载安装包:http://zookeeper.apache.org/release ...
- spring bean初始化及销毁你必须要掌握的回调方法
spring bean在初始化和销毁的时候我们可以触发一些自定义的回调操作. 初始化的时候实现的方法 1.通过java提供的@PostConstruct注解: 2.通过实现spring提供的Initi ...
- 【NOIP】普及组2009 细胞分裂
[算法]数论 [题解]均分的本质是A整除B,A整除B等价于A的质因数是B的子集. 1.将m1分解质因数,即m1=p1^a1*p2^a2*...*pk^ak 所以M=m1^m2=p1^(a1*m2)*p ...
- 【洛谷 P4219】 [BJOI2014]大融合(LCT)
题目链接 维护子树信息向来不是\(LCT\)所擅长的,所以我没搞懂qwq 权当背背模板吧.Flash巨佬的blog里面写了虽然我没看懂. #include <cstdio> #define ...
- NB二人组(一)----堆排序
堆排序前传--树与二叉树简介 特殊且常用的树--二叉树 两种特殊的二叉树 二叉树的存储方式 二叉树小结 堆排序 堆这个玩意....... 堆排序过程: 构造堆: 堆排序的算法程序(程序需配合着下图理 ...
- 深入理解Spring系列之二:BeanDefinition解析
转载 https://mp.weixin.qq.com/s?__biz=MzI0NjUxNTY5Nw==&mid=2247483814&idx=1&sn=ddf49931d55 ...
- cin循环输入控制问题
之前写一个简单的输入节点值自动生成链表的测试程序,发现cin的输入控制好像在VC++6.0和VS2010中不一样,特此记录. 现在有以下代码: vector<int> ivec; int ...
- slave->pxc后GTID不一致
以下两个参数在两个节点是对得上的. | wsrep_last_applied | 3363764 | | wsrep_last_committed | 3363764 但show master sta ...