树论讲解——最近公共祖先(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 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖 ...
随机推荐
- CSS选择器-常用搜集
标签选择器: div{ font-size=10px; color=red; background-color=yello; width=200px; height=200px; } <div& ...
- DOM使用
DOM树模型 document |-html |-head |-.... |-body |-..... 要解析页面的前提是要拿到一个对象,然后利用树之间前后的关系进行对象的遍历和操作. 在DHTML的 ...
- NYOJ 409 郁闷的C小加(三) (字符串处理)
题目链接 描述 聪明的你帮助C小加解决了中缀表达式到后缀表达式的转换(详情请参考"郁闷的C小加(一)"),C小加很高兴.但C小加是个爱思考的人,他又想通过这种方法计算一个表达式的值 ...
- Verilog笔记.4.inout端口
inout是一个双向端口,实现为使用三态门,第三态为高阻态‘z’. 在实际电路中高阻态意味着响应的管脚悬空.断开. 当三态门的控制信号为真时,三态门选通,作输出端口使用:控制信号为假时,三态门是高阻态 ...
- kippo蜜罐搭建
kippo蜜罐搭建 总结一下kippo蜜罐搭建的方法,centos系统.kippo-master.zip的安装包 (gcc,python-devel,pip,twisted==13.10,pycryp ...
- windows环境下搭建Redis集群
转载请注明出处,原文章地址: https://www.cnblogs.com/tommy-huang/p/6240083.html Redis集群: 如果部署到多台电脑,就跟普通的集群一样:因为Red ...
- Balanced and stabilized quicksort method
The improved Quicksort method of the present invention utilizes two pointers initialized at opposite ...
- C++ Class与Struct的区别
转自某楼层的回复http://bbs.csdn.net/topics/280085643 首先,讨论这个问题应该仅从语法上讨论,如果讨论不同人之间编程风格上的差异,那这个问题是没有答案的.毕竟不同的人 ...
- MGR Switch Muti-Primary to single_primary
MGR Muti-Primary 切换 single_primary 模式 原因:因为希望做ProxySQL+MGR之间Proxy层的消耗测试,需要把原有的MGR多主改为单主模式. 修改MGRgrou ...
- nginx 各种配置
first : mkdir /usr/local/nginx/conf/vhosts{网站配置}/usr/local/nginx/conf/vhosts/test.conf : server { li ...