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. shell脚本基本知识点

    Shell 是一个用C语言编写的程序,它是用户使用Linux的桥梁.用户通过这个界面访问Linux操作系统内核的服务.Shell既是一种命令语言,又是一种程序设计语言. 1.Shell 环境 Shel ...

  2. java多线程基本概述(七)——join()方法

    在很多情况下,主线程创建并启动子线程,如果子线程中有大量的耗时运算,主线程将早于子线程结束,如果想让主线程等待子线程结束后再结束,那么我们可以使用join()方法.调用join()方法的意思是当前线程 ...

  3. openssl命令行-证书认证

      命令1: openssl genrsa -out root.key 1024 产生一个root.key的私钥 命令2: openssl req -key root.key -new -out ro ...

  4. Web API 之SelfHost与OwinSelfHots加载外部程序

       下面就一些web api的一些基础内容进行阐述,然后就web api宿主承载中的实际业务问题进行解决 HttpController      HttpController的激活是由处于消息处理管 ...

  5. List<Object>对象集合一些扩展方法

    // 商品集合信息            List<Product> list = new List<Product>()            {               ...

  6. smarty的缓冲

    首先在main文件夹里面建一个文件 huancun.php   然后在template文件夹里面建一个文件 huancun.html   huancun.php中的内容为: require(" ...

  7. 纯JS单页面赛车游戏代码分享

    分享一个以前写的小游戏,纯js游戏,代码很简单.欢迎大家来喷呦! 效果图: 代码展示://直接复制到html文件即可 支持IE9+版本 <!DOCTYPE html> <html&g ...

  8. nginx参数的详细说明

    #开启进程数 <=CPU数 worker_processes 1; #错误日志保存位置 #error_log logs/error.log; #error_log logs/error.log ...

  9. linux性能之iostat

    在使用linux系统的过程中,总是可能需要当前io性能的状态信息是怎么样?这里就就是一下iostat,可以通过iostat来初步查看io的状态信息. 1.常用方式 iostat -xdk 1 10 或 ...

  10. 转Fiddler 构造http请求

    今天使用Fiddler构造一个POST请求,server端的PHP脚本的 $_POST数组中怎么也获取不到值,后来偶然发现是因为缺少了一个http头:Content-Type: application ...