最近公共祖先(LCA)问题
最近公共祖先
定义:给定一颗有根树,若结点 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)问题的更多相关文章
- Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)
Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...
- 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 之间所有路径中深度最小的节 点,所 ...
- POJ 1470 Closest Common Ancestors (最近公共祖先LCA 的离线算法Tarjan)
Tarjan算法的详细介绍,请戳: http://www.cnblogs.com/chenxiwenruo/p/3529533.html #include <iostream> #incl ...
- 【Leetcode】查找二叉树中任意结点的最近公共祖先(LCA问题)
寻找最近公共祖先,示例如下: 1 / \ 2 3 / \ / \ 4 5 6 7 / \ ...
- 最近公共祖先LCA(Tarjan算法)的思考和算法实现
LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了f ...
- 查找最近公共祖先(LCA)
一.问题 求有根树的任意两个节点的最近公共祖先(一般来说都是指二叉树).最近公共祖先简称LCA(Lowest Common Ancestor).例如,如下图一棵普通的二叉树. 结点3和结点4的最近公共 ...
- 最近公共祖先(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- ...
随机推荐
- MindManager安装、激活
MindManager安装 MindManager激活
- console.log 如何打印对象
问题描述: var obj={a:1,b:2}; console.log(obj); 控制台返回的值是object. 解决方案: console.log(JSON.stringify(obj))
- EntityFramework 插入自增ID主从表数据
原因: 数据库中的两个表是主从表关系,但是没有建外键,而表的id用的是数据库的自增整数,导致在使用EF导入主从表数据时,需要先保存主表数据,取到 主表的自增id后才能插入从表数据,这样循环之下,数据插 ...
- django urls.py 中的name 使用方法
使用场景: 当我们在url的时候,一般情况下都是使用很明确的url地址.如在网页里面使用<a href="/login">登录</a>.像这样的链接有很 多 ...
- 在IDEA离线安装lombok插件
1.打开,找到自己IDEA版本,idea http://plugins.jetbrains.com/plugin/6317-lombok/versions 2.下载,导入安装
- acm数论之旅--欧拉函数的证明
随笔 - 20 文章 - 0 评论 - 73 ACM数论之旅7---欧拉函数的证明及代码实现(我会证明都是骗人的╮( ̄▽ ̄)╭) https://blog.csdn.net/chen_ze_hua ...
- mybatis批量插入和更新
批量插入 <insert id="add" parameterType="java.util.List"> insert all <forea ...
- YouTube为创作者提供了更多赚钱的途径
编辑 | 于斌 出品 | 于见(mpyujian) 大家提到YouTube可能还有些陌生,只是听说过,但因为一些原因并没有实际应用过,但其实YouTube就是设立在美国的一个视频分享网站,让使用者上载 ...
- socketserver tcp黏包
socket (套接字) tcp(黏包现象原因) 传输中由于内核区缓冲机制(等待时间,文件大小),会在 发送端 缓冲区合并连续send的数据,也会出现在 接收端 缓冲区合并recv的数据给指定port ...
- 基于SILVACO ATLAS的a-IGZO薄膜晶体管二维器件仿真(02)
Silvaco的破解用了好久好久,而且之后拷了上次例子的代码,Tonyplot的输出存在报错,还是四连. 当然这个点一下还是会出图的.但是,源代码稍微改了下结构,又有报错,而且程序直接终止. go a ...