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. kmp(看毛片)算法

    别人的两篇博客. 传送门1 传送门2 其中T为主串,P为模式串. 其实就是在T中找P. 其中next数组存的是"部分匹配值". "部分匹配值"就是"前 ...

  2. jQuery扩展函数设置所有对象只读

    jQuery(function ($) {             $.fn.disable = function () {                 return this.each(func ...

  3. Spring+SpringMVC+MyBatis+easyUI整合优化篇(十一)数据层优化-druid监控及慢sql记录

    本文提要 前文也提到过druid不仅仅是一个连接池技术,因此在将整合druid到项目中后,这一篇文章将去介绍druid的其他特性和功能,作为一个辅助工具帮助提升项目的性能,本文的重点就是两个字:监控. ...

  4. open vswitch常用操作

    以下操作都需要root权限运行,在所有命令中br0表示网桥名称,eth0为网卡名称. 添加网桥: #ovs-vsctl add-br br0 列出open vswitch中的所有网桥: #ovs-vs ...

  5. MVC实现SSO

    近来工作无事,想做个SSO, 之前做过一个项目用到SSO,自己也没有看明白是个什么东西.现在正好有时间,所以想研究下. 先是从网上看到了SSO的思路: 三个站点:SiteA,SiteB,SiteMai ...

  6. MarkDown 学习笔记

    MarkDown是一种适用于网络的书写语言,可以帮助你快速书写文档,不必再纠结文档排版的问题.并且它的语法简单,学习成本低,程序员必备技能...助你快速书写技术文档.文章. 用于书写 MarkDown ...

  7. js小数处理

    js中的小数处理   先说说Math的几个方法: 1.Math.floor(x)   返回不大于当前数的最大整数. 我的记法:floor 直译 地板  也就是不大于的的意思 (x-0.5 四舍五入取整 ...

  8. 树型权限管理插件:jQuery Tree Multiselect详细使用指南

    1.认识jQuery Tree Multiselect 这个插件允许用户以树型的形式来呈现列表复选框的选择.多用于权限管理中用于分配不同的权限.使用文档,请参考:     https://github ...

  9. 阿里云centos 安装和配置 DokuWiki

    安装 1) 添加虚拟主机:由于我的 阿里云CentOs服务器 安装了oneinstack的一键部署PHP.JAVA.Nginx等环境,所以域名配置很方便,照着文档一步一步做就可以了 cd /root/ ...

  10. Kafka协议兼容性改进

    在Kafka 0.10.2.0之前,Kafka服务器端和客户端版本之间的兼容性是"单向"的,即高版本的broker可以处理低版本client的请求.反过来,低版本的broker不能 ...