最近公共祖先

定义:给定一颗有根树,若结点 z 既是 x 的祖先,也是 y 的祖先,则称 z 是 x,y 的公共祖先。在 x,y 所有的公共祖先中,深度最大的一个称为 x,y 的最近公共祖先,简称\(LCA(x,y)\)。

求解最近公共祖先一般有三种解法:向上标记法,树上倍增法和 Tarjan 算法。

1.向上标记法

即对于任何两个结点 x , y ,分别从x y 向上走并标记它们所有经过的节点,第一次相遇的节点即为最近公共祖先。其时间复杂度最坏为\(O(n^2)\)。

2.树上倍增法

树上倍增法在很多问题都有很广泛的应用,当然最近公共祖先也可以用树上倍增算法来求,其基本思想是:

设d[x]>=d[y],其中d[x]表示x的深度,然后利用二进制思想,将x不断调整直至和y在同一深度,检查此时x和y是否相等,若相等即为最近公共祖先,否则将x,y一起向上调整直至相等,此时则为最近公共祖先。

该算法时间复杂度为:\(O((n+m)logn)\)具体代码(裸题,求权值)为:

#include<bits/stdc++.h>

using namespace std;
const int maxn=5e5+10;
int head[maxn],dis[maxn],d[maxn],f[maxn][20];
struct Edge
{
int nex,to,val;
}edge[maxn<<1];
int T,t,n,m,tot;
queue<int> q;
void init()
{
tot=0;
memset(head,-1,sizeof(head));
memset(d,0,sizeof(d));
}
void add(int from,int to,int val)
{
edge[++tot].to=to;
edge[tot].val=val;
edge[tot].nex=head[from];
head[from]=tot;
}
void bfs()
{
q.push(1);
d[1]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=head[x];i!=-1;i=edge[i].nex)
{
int y=edge[i].to;
if(d[y]) continue;
d[y]=d[x]+1;
dis[y]=dis[x]+edge[i].val;
f[y][0]=x;
for(int j=1;j<=t;++j)
f[y][j]=f[f[y][j-1]][j-1];
q.push(y);
}
}
}
int lca(int x,int y)
{
if(d[x]<d[y]) swap(x,y);
for(int i=t;i>=0;--i)
if(d[f[x][i]]>=d[y]) x=f[x][i];
if(x==y) return x;
for(int i=t;i>=0;--i){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
} int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&n,&m);
t=(int)(log(n)/log(2))+1;
init();
for(int i=1;i<n;++i){
int a,b,val;
scanf("%d %d %d",&a,&b,&val);
add(a,b,val);
add(b,a,val);
}
bfs();
for(int i=1;i<=m;++i){
int x,y;
scanf("%d %d",&x,&y);
printf("%d\n",dis[x]+dis[y]-2*dis[lca(x,y)]);
}
}
}

3.Tarjan算法

Tarjan算法本质上是用并查集对“向上标记法”的优化。它是一个离线算法(即将所有输入都输入完成后一起输出);其时间复杂度为:\(O(n+m)\)。

该算法将树中所有结点分成了三类:访问并回溯过,访问但为回溯过,未访问三类。该三类点分别标记为2,1和0。该算法的思想是当一个结点被标记为2时,则将它所在集合合并到它的父节点所在的集合中,这就相当于每个完成回溯的结点都有一个指针指向其父节点,所以只需查询y所在集合的代表元素,就等价与从y一直向上走到一个开始递归但尚未回溯的结点,即\(LCA(x,y)\)。

还是上面的题面,看代码:

#include<bits/stdc++.h>

using namespace std;
const int maxn=5e4+10;
const int maxe=210;
const int inf=0x3f3f3f3f;
struct Edge
{
int nex,to,val;
}edge[maxn<<1];
int T,n,m,t,tot;
int head[maxn],fa[maxn],v[maxn],dis[maxn],ans[maxe];
vector<int> query[maxn],query_id[maxn];
void init()
{
tot=0;
memset(head,-1,sizeof(head));
memset(dis,0,sizeof(dis));
memset(v,0,sizeof(v));
for(int i=1;i<=n;++i){
fa[i]=i;
query[i].clear();
query_id[i].clear();
}
}
void add(int from,int to,int val)
{
edge[++tot].to=to;
edge[tot].val=val;
edge[tot].nex=head[from];
head[from]=tot;
}
void add_query(int x,int y,int id)
{
query[x].push_back(y);
query_id[x].push_back(id);
query[y].push_back(x);
query_id[y].push_back(id);
}
int ffind(int x)
{
return x==fa[x]?x:fa[x]=ffind(fa[x]);
}
void tarjan(int x)
{
v[x]=1;
for(int i=head[x];i!=-1;i=edge[i].nex)
{
int y=edge[i].to;
if(v[y]) continue;
dis[y]=dis[x]+edge[i].val;
tarjan(y);
fa[y]=x;
}
for(int i=0;i<query[x].size();++i)
{
int y=query[x][i],id=query_id[x][i];
if(v[y]==2){
int lca=ffind(y);
ans[id]=min(ans[id],dis[x]+dis[y]-2*dis[lca]);
}
}
v[x]=2;
} int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&n,&m);
init();
for(int i=1;i<n;++i){
int a,b,val;
scanf("%d %d %d",&a,&b,&val);
add(a,b,val);
add(b,a,val);
}
for(int i=1;i<=m;++i){
int x,y;
scanf("%d %d",&x,&y);
if(x==y) ans[i]=0;
else{
add_query(x,y,i);
ans[i]=inf;
}
}
tarjan(1);
for(int i=1;i<=m;++i)
printf("%d\n",ans[i]);
}
system("pause");
}

最近公共祖先(LCA)问题的更多相关文章

  1. Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)

    Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...

  2. POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)

    POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...

  3. POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)

    POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...

  4. [模板] 最近公共祖先/lca

    简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...

  5. 【lhyaaa】最近公共祖先LCA——倍增!!!

    高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...

  6. POJ 1470 Closest Common Ancestors (最近公共祖先LCA 的离线算法Tarjan)

    Tarjan算法的详细介绍,请戳: http://www.cnblogs.com/chenxiwenruo/p/3529533.html #include <iostream> #incl ...

  7. 【Leetcode】查找二叉树中任意结点的最近公共祖先(LCA问题)

    寻找最近公共祖先,示例如下: 1 /           \ 2           3 /    \        /    \ 4    5      6    7 /    \          ...

  8. 最近公共祖先LCA(Tarjan算法)的思考和算法实现

    LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了f ...

  9. 查找最近公共祖先(LCA)

    一.问题 求有根树的任意两个节点的最近公共祖先(一般来说都是指二叉树).最近公共祖先简称LCA(Lowest Common Ancestor).例如,如下图一棵普通的二叉树. 结点3和结点4的最近公共 ...

  10. 最近公共祖先(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- ...

随机推荐

  1. ORACLE_BASE、ORACLE_HOME有什么区别

    ORACLE_BASE.ORACLE_HOME有什么区别   ORACLE_BASE下是admin和productORACLE_HOME下则是ORACLE的命令.连接库.安装助手.listener等等 ...

  2. dbGet(二.一)hinst

    hinst hierarchical insts Parent Object bndry,group,hInstTerm,hTerm, inst,ptn,topCell,vCell Child Obj ...

  3. python pip 安装常用库如何使用国内镜像源

    最近通过python安装一些常用库发现下载速度特别慢,而且还会出现无法安装等错误,尝试找了一些国内的镜像源,发现阿里云的速度很快,这里做个记录,并分享给大家. 国内镜像源 阿里云:http://mir ...

  4. 开源协议:LGPL协议、OSGi协议

    本文介绍开源的协议. LGPL 是 GNU Lesser General Public License (GNU 宽通用公共许可证)的缩写形式,旧称 GNU Library General Publi ...

  5. National Contest for Private Universities (NCPU), 2019 C Boxes(双向链表)

    题目中的要求如果x在y的左边,不需要移动,x在y的右边,2操作不需要移动. 有一个问题是,如果x与y相邻,这时的swap操作变成了三个而不是四个,这点尤其需要注意,不然就会死循环.注意x是和y相邻,这 ...

  6. 1010 Radix

    1010 Radix 注意点 如111 1 1 10类似情况下,若n为个位数,如果本身比另一个数小,则多少的进制都是没有用的(可能会造成空循环而超时),不过好像没有这么一个测试用例 进制应该比最少数据 ...

  7. [Python] Tkinter的食用方法_02_LabelFrame RadioButton CheckButton

    #开始 Python的话 我是看的小甲鱼的视频 所以代码也是小甲鱼的修改版 本博客这里只是学习记录 小甲鱼是个很棒的老师,虽然经常飙车.... #第一个代码 from tkinter import * ...

  8. html文件中引入html文件

    一般用于网站提取公共部分的导航栏等 第一种方式:<iframe>标签 在body标签第一行加<iframe>标签 <body> <iframe src=&qu ...

  9. 数制的运用-CodeForces - 535B

    题解: 因为每一位只可能是4或者7,可以类比二进制的思想. 基数为2,每一位的权值为2i-1:数字4表示的大小为1*2i-1:数字7表示的大小为2*2i-1. 将给定的n按照这种方法进行分解,求和.即 ...

  10. svn的安装和备份

    svn的安装: yum install subversion -y mkdir /svn 创建用来存放svn版本库的目录 svnadmin create /svn/first  创建一个svn的版本库 ...