LCA

定义

最近公共祖先简称 LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。

性质

  1. 如果 不为 的祖先并且 不为 的祖先,那么 分别处于 的两棵不同子树中;
  2. 前序遍历中, 出现在所有 中元素之前,后序遍历中 则出现在所有 中元素之后;
  3. 两点集并的最近公共祖先为两点集分别的最近公共祖先的最近公共祖先,即
  4. 两点的最近公共祖先必定处在树上两点间的最短路

倍增算法

过程

倍增算法是最经典的 LCA 求法,他是朴素算法的改进算法。通过预处理 数组,游标可以快速移动,大幅减少了游标跳转次数。 表示点 的第 个祖先。 数组可以通过 dfs 预处理出来。

现在我们看看如何优化这些跳转: 在调整游标的第一阶段中,我们要将 两点跳转到同一深度。我们可以计算出 两点的深度之差,设其为 。通过将 进行二进制拆分,我们将 次游标跳转优化为「 的二进制表示所含 1 的个数」次游标跳转。 在第二阶段中,我们从最大的 开始循环尝试,一直尝试到 (包括 ),如果 ,则 ,那么最后的 LCA 为

性质

倍增算法的预处理时间复杂度为 ,单次查询时间复杂度为 。 另外倍增算法可以通过交换 fa 数组的两维使较小维放在前面。这样可以减少 cache miss 次数,提高程序效率。

例题

可先求出 LCA,再结合性质 进行解答。也可以直接在求 LCA 时求出结果。

参考代码

|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| | #include <cstdio> #include <cstring> #include <iostream> #include <vector> #define MXN 50007 using namespace std; std::vector<int> v[MXN]; std::vector<int> w[MXN]; int fa[MXN][31], cost[MXN][31], dep[MXN]; int n, m; int a, b, c; // dfs,用来为 lca 算法做准备。接受两个参数:dfs 起始节点和它的父亲节点。 void dfs(int root, int fno) { // 初始化:第 2^0 = 1 个祖先就是它的父亲节点,dep 也比父亲节点多 1。 fa[root][0] = fno; dep[root] = dep[fa[root][0]] + 1; // 初始化:其他的祖先节点:第 2^i 的祖先节点是第 2^(i-1) 的祖先节点的第 // 2^(i-1) 的祖先节点。 for (int i = 1; i < 31; ++i) { fa[root][i] = fa[fa[root][i - 1]][i - 1]; cost[root][i] = cost[fa[root][i - 1]][i - 1] + cost[root][i - 1]; } // 遍历子节点来进行 dfs。 int sz = v[root].size(); for (int i = 0; i < sz; ++i) { if (v[root][i] == fno) continue; cost[v[root][i]][0] = w[root][i]; dfs(v[root][i], root); } } // lca。用倍增算法算取 x 和 y 的 lca 节点。 int lca(int x, int y) { // 令 y 比 x 深。 if (dep[x] > dep[y]) swap(x, y); // 令 y 和 x 在一个深度。 int tmp = dep[y] - dep[x], ans = 0; for (int j = 0; tmp; ++j, tmp >>= 1) if (tmp & 1) ans += cost[y][j], y = fa[y][j]; // 如果这个时候 y = x,那么 x,y 就都是它们自己的祖先。 if (y == x) return ans; // 不然的话,找到第一个不是它们祖先的两个点。 for (int j = 30; j >= 0 && y != x; --j) { if (fa[x][j] != fa[y][j]) { ans += cost[x][j] + cost[y][j]; x = fa[x][j]; y = fa[y][j]; } } // 返回结果。 ans += cost[x][0] + cost[y][0]; return ans; } int main() { // 初始化表示祖先的数组 fa,代价 cost 和深度 dep。 memset(fa, 0, sizeof(fa)); memset(cost, 0, sizeof(cost)); memset(dep, 0, sizeof(dep)); // 读入树:节点数一共有 n 个。 scanf("%d", &n); for (int i = 1; i < n; ++i) { scanf("%d %d %d", &a, &b, &c); ++a, ++b; v[a].push_back(b); v[b].push_back(a); w[a].push_back(c); w[b].push_back(c); } // 为了计算 lca 而使用 dfs。 dfs(1, 0); // 查询 m 次,每一次查找两个节点的 lca 点。 scanf("%d", &m); for (int i = 0; i < m; ++i) { scanf("%d %d", &a, &b); ++a, ++b; printf("%d\n", lca(a, b)); } return 0; } |

相关推荐
飞yu流星44 分钟前
C++ 函数 模板
开发语言·c++·算法
pursuit_csdn1 小时前
力扣 74. 搜索二维矩阵
算法·leetcode·矩阵
labuladuo5201 小时前
洛谷 P8703 [蓝桥杯 2019 国 B] 最优包含(dp)
算法·蓝桥杯·动态规划
Milk夜雨2 小时前
C语言冒泡排序教程简介
数据结构·算法·排序算法
委婉待续2 小时前
redis的学习(三)
数据结构·算法
一直学习永不止步2 小时前
LeetCode题练习与总结:随机翻转矩阵--519
java·数学·算法·leetcode·哈希表·水塘抽样·随机化
xiao--xin2 小时前
LeetCode100之组合总和(39)--Java
java·开发语言·算法·leetcode·回溯
IT猿手3 小时前
部落竞争与成员合作算法(CTCM)求解5个无人机协同路径规划(可以自定义无人机数量及起始点),MATLAB代码
深度学习·算法·机器学习·matlab·无人机·无人机、
GISer_Jing4 小时前
React中 Reconciliation算法详解
前端·算法·react.js
自律的kkk4 小时前
洛谷:P1540 [NOIP2010 提高组] 机器翻译
人工智能·算法·自然语言处理·机器翻译