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. bigdecimal更精确的浮点处理方式

    Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算.双精度浮点型变量double可以处理16位内有效数,超过16位,double可能会出现内存 ...

  2. 读书笔记 effective c++ Item 51 实现new和delete的时候要遵守约定

    Item 50中解释了在什么情况下你可能想实现自己版本的operator new和operator delete,但是没有解释当你实现的时候需要遵守的约定.遵守这些规则并不是很困难,但是它们其中有一些 ...

  3. JS存在性

    var myObject = { a:2 }; ("a" in myObject);//true ("b" in myObject);//false myObj ...

  4. C/C++对bool operator < (const p &a)const的认识,运算符重载详解(杂谈)

    下面来进行这段代码的分析: struct node {  //定义一个结构体node(节点)    int x;    int y;    int len;   //node中有3个成员变量x,y,l ...

  5. collection and map and Collections

    两者的区别: 两者都是接口: Collectoin是java集合框架的一个顶级接口,存储的元素可以是任意类型的对象: Map是java集合框架的映射接口,以键值对的形式存储对象: 也就是说,colle ...

  6. Linux Bootup Time

    Linux Bootup Time 英文原文地址:http://elinux.org/Boot_Time 1.   简介 启动时间这一话题包括很多子话题,比如启动时间的衡量.启动时间的分析.人为因素分 ...

  7. Spring框架基础知识

    本人博客文章网址:https://www.peretang.com/basic-knowledge-of-spring-framework/ Spring框架简介 Spring , 一个开源的框架 , ...

  8. 关于获取URL中传值的解决方法

    在我们页面的URL中包含着很多信息,包括域名,协议等等这里就不一一介绍了),对于我们开发者而言,使用比较多的就是页面之间的传值.为什么要页面传值呢?很简单,当你在浏览一个商品页面的时候你要看到一个商品 ...

  9. 01背包Bone Collector

    好几天没写博客了,整天忙着打比赛,希望能有参加省赛的资格,不容易啊. 今天复习背包,之前集训讲过,现在又忘了,昨天杭电校赛刚好有一题背包,居然不会做了,好尴尬,重新复习一下. https://vjud ...

  10. VopSdk一个高逼格微信公众号开发SDK(源码下载)

    看之前回复很多说明大家很有热情&文章被误删掉了,不想让有的朋友错失这个高逼格的东西,现在重新发布,这次就直接放出源码,文章最末下载地址. 看之前回复很多说明大家很有热情&文章被误删掉了 ...