LCA

在有根树中,两个节点 u 和 v 的公共祖先中距离最近的那个被称为最近公共祖先(LCA,Lowest Common Ancestor)。

有多种算法解决 LCA 或相关的问题。

基于二分搜索的算法

首先搜索树中各个节点的深度;

const int MAXN = 4e4 + 5;  // 最大节点数
const int LOG_N = 60; // 树的最大深度
vector<int> G[MAXN]; // 树
int depth[MAXN]; // 节点深度
int parent[LOG_N][MAXN]; // parent[k][i]表示 i 向上走 2^k 步能到达的节点
void dfs(int pre, int u, int d)
{
parent[0][u] = pre;
depth[u] = d;
for(int i = 0; i < G[u].size(); i++)
{
int v = G[u][i];
if(v != pre) dfs(u, v, d + 1);
}
}

对于任意节点,通过节点和其父节点的信息,都能得到其和父亲的父亲节点的关系,即可以得到向上走 2 步所能到达的节点的值;

那么,同样可以得到向上走 4 步所能到达节点的值;后面同理。

而树的深度很小,所以可以预处理所有点;

void init()
{
int root = 1;
dfs(-1, root, 0);
for(int k = 1; k < LOG_N; k++)
{
for(int i = 1; i <= n; i++)
{
if(parent[k - 1][i] < 0) parent[k][i] = -1;
else parent[k][i] = parent[k - 1][parent[k - 1][i]];
}
}
}

计算 LCA 时,首先让它们到达同一深度,在同时向上搜索最近公共祖先即可。

int lca(int u, int v)
{
if(depth[u] > depth[v]) swap(u, v);
for(int i = 0; i < LOG_N; i++) // u 和 v 向上走到同一深度
{
if((depth[v] - depth[u]) >> i & 1) // 把 (depth[v] - depth[i]) 化成二进制后可以看到,就是找到所有 1 的位置
{
v = parent[i][v];
}
}
if(v == u) return u;
for(int i = LOG_N - 1; i >= 0; i--) // 找 lca
{
if(parent[i][u] != parent[i][v]) // 如果相同,那么一定是公共祖先或公共祖先之上的节点
{
u = parent[i][u];
v = parent[i][v];
}
}
return parent[0][u];
}

Tarjan 离线算法

hdu2586

一道模板题,求二叉树中两个节点的最短距离,就是 dis[u] + dis[v] - 2 * dis[lca(u,v)]

Tarjan离线算法,先读入所有查询,直接算出所有答案。

其实就是利用 DFS 遍历二叉树的特性,以及并查集的优化,

首先,从 1 向下搜,一直搜到 8 ,在这过程中,对于查询的边 u - v ,节点 u 对应的 v 已经访问过(如 4 - 2),那么 found(2) 就是 LCA(4, 2) ;

搜到 8 ,会回到 4 -> 9 -> 4 -> 2,再去搜 5 ,如果查询的节点是 (5 - 8) 5 对应的 8 已被访问过,那么 LCA(5, 8) = found(8),因为到现在为止的 DFS 都在 2 这个节点之下,所以只要没回到 1, found(2) = 2 保持不变,即 LCA(5, 8) = found(2) = 2,后面的同理;

所以关键就是遍历完一个节点的所有子树之后在去指定这个节点的父亲节点;

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
typedef long long ll;
const int INF = 1e9;
const int MAXN = 4e4 + 10;
using namespace std;
typedef pair<int, int> P;
int n, m;
int p[MAXN]; // 并查集祖先节点
int q[MAXN]; // 对应第几次查询的 lca
int ex[MAXN], ey[MAXN]; // 记录查询的边
int vis[MAXN]; // 标记数组
int dis[MAXN]; // 离根节点的距离
vector<P> G[MAXN];
vector<P> edges[MAXN];
int found(int x)
{
return x == p[x] ? x : (p[x] = found(p[x]));
}
void tarjan(int pre, int u, int len)
{
vis[u] = 1;
dis[u] = dis[pre] + len;
for(int i = 0; i < edges[u].size(); i++)
{
P v = edges[u][i];
if(vis[v.first]) q[v.second] = found(v.first);
}
for(int i = 0; i < G[u].size(); i++)
{
P v = G[u][i];
if(v.first != pre)
{
tarjan(u, v.first, v.second);
p[v.first] = u;
}
}
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
memset(vis, 0, sizeof vis);
for(int i = 0; i <= n; i++)
{
p[i] = i;
vis[i] = 0;
G[i].clear();
edges[i].clear();
}
for(int i = 1; i < n; i++)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
G[x].push_back(P(y, z));
G[y].push_back(P(x, z));
}
for(int i = 0; i < m; i++)
{
int x, y;
scanf("%d%d", &x, &y);
ex[i] = x; ey[i] = y;
edges[x].push_back(P(y, i));
edges[y].push_back(P(x, i));
}
tarjan(0, 1, 0);
for(int i = 0; i < m; i++)
{
printf("%d\n", dis[ex[i]] + dis[ey[i]] - 2 * dis[q[i]]);
}
}
return 0;
}

LCA——求解最近公共祖先的更多相关文章

  1. 【LCA求最近公共祖先+vector构图】Distance Queries

    Distance Queries 时间限制: 1 Sec  内存限制: 128 MB 题目描述 约翰的奶牛们拒绝跑他的马拉松,因为她们悠闲的生活不能承受他选择的长长的赛道.因此他决心找一条更合理的赛道 ...

  2. CodeVs.2370 小机房的树 ( LCA 倍增 最近公共祖先)

    CodeVs.2370 小机房的树 ( LCA 倍增 最近公共祖先) 题意分析 小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上.有一天, ...

  3. LCA(最近公共祖先)——Tarjan

    什么是最近公共祖先? 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵树上距离最近的公共祖先节点. ...

  4. LCA(最近公共祖先)

    学习链接:https://baike.baidu.com/item/%E4%BC%B8%E5%B1%95%E6%A0%91/7003945?fr=aladdin 求LCA的方法有很多,在这里就只介绍一 ...

  5. LCA(最近公共祖先)离线算法Tarjan+并查集

    本文来自:http://www.cnblogs.com/Findxiaoxun/p/3428516.html 写得很好,一看就懂了. 在这里就复制了一份. LCA问题: 给出一棵有根树T,对于任意两个 ...

  6. poj 1330 Nearest Common Ancestors(LCA:最近公共祖先)

    多校第七场考了一道lca,那么就挑一道水题学习一下吧= = 最简单暴力的方法:建好树后,输入询问的点u,v,先把u全部的祖先标记掉,然后沿着v->rt(根)的顺序检查,第一个被u标记的点即为u, ...

  7. 算法模板——LCA(最近公共祖先)

    实现的功能如下——在一个N个点的无环图中,共有N-1条边,M个访问中每次询问两个点的距离 原理——既然N个点,N-1条边,则说明这是一棵树,而且联通.所以以1为根节点DFS建树,然后通过求两点的LCA ...

  8. LCA(最近公共祖先)算法

    参考博客:https://blog.csdn.net/my_sunshine26/article/details/72717112 首先看一下定义,来自于百度百科 LCA(Lowest Common ...

  9. LCA(最近公共祖先)--tarjan离线算法 hdu 2586

    HDU 2586 How far away ? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/ ...

随机推荐

  1. ASP.NET CORE部署到Linux

    ASP.NET CORE部署到CentOS中 在Linux上安装.NET Core 参考:https://www.microsoft.com/net/core#linuxcentos 配置Nginx ...

  2. LeanCloud 调研报告

    LeanCloud 是一家做后端即服务(BaaS)的厂商,目标是让移动互联网开发者能更加方便的开发应用. 出于工作关系,对 leancloud 进行了一番调研:主要目标是学习其后端即服务的产品化思路等 ...

  3. bzoj1013 [JSOI2008]球形空间产生器

    Description 有一个球形空间产生器能够在n维空间中产生一个坚硬的球体.现在,你被困在了这个n维球体中,你只知道球面上n+1个点的坐标,你需要以最快的速度确定这个n维球体的球心坐标,以便于摧毁 ...

  4. 前端JS来控制选中的项

    < script type = "text/javascript" > function change(){ document.getElementById(" ...

  5. 蓝桥杯-加法变乘法-java

    /* (程序头部注释开始) * 程序的版权和版本声明部分 * Copyright (c) 2016, 广州科技贸易职业学院信息工程系学生 * All rights reserved. * 文件名称: ...

  6. JavaScript面向对象编程—this详解

      this详解 作者的话 在JavaScriptOPPt面向对象编程中,this这位老大哥,相信大家不会陌生.大家在遇到this时,很多朋友难免会有个疑问:"这个this是什么,它到底指向 ...

  7. JS常用方法【私房菜-笔记】-持续整理中

    //记录一下前端开发中 JS常用的方法等,持续收集整理中 ---------------------------------------------------------- //处理键盘事件 禁止后 ...

  8. poj 1008

    #include<iostream>#include<string> using namespace std;string hname[19] = { "pop&qu ...

  9. hdu4639 hehe 递推

    此题为递推题 现场比赛中由于心态问题没能快速推出来定义f[i]为i个连续的he可以表示的语意的个数 则如果第i个he单独考虑f[i]=f[i-1];如果将第i个he和第i-1个he组合 则其只能表示为 ...

  10. hdu2571 命运 简单DP

    简单dp 状态方程很好想,主要是初始化.... 代码: #include<iostream> #include<cstdlib> #include<cstdio> ...