首页 > 其他分享 >LCA 学习笔记

LCA 学习笔记

时间:2022-12-28 09:44:35浏览次数:59  
标签:lv int 笔记 学习 num LCA lvl 节点

概念

LCA

LCA (Lowest Common Ancestor),即最近公共祖先,指的是一棵树中任意两个节点深度最大的共同祖先。

有啥用

树有一个性质,两点之间有且只有一条简单路径,如果我们把 1 号节点作为根,则任意两点 \(x,y\) 的简单路径就是 \(x\) 到 \(lca(x,y)\) 再到 \(y\)。

所以这个东西大有用处,于是如何快速的求出两点之间的 \(LCA\) 就非常重要了。

朴素算法

我们通过一遍深搜处理出所有节点的父节点以及深度(这里的深度指到根节点的路径上的节点个数,与边权无关),然后对于每一次询问,我们先把更深的节点一步一步往上跳到与另一个节点一样的深度,然后再一起一步一步往上跳,直到跳到同一个节点,这个节点就是它们的 LCA。

这样每次询问的时间复杂度是树的深度,在精心构造的数据中可以达到 \(O(n)\),还是比较慢的。

倍增

思想

我们设 \(p_{i,j}\) 表示 \(i\) 号节点的第 \(2^j\) 级祖先的节点编号(1 级祖先即为父节点,0 级祖先为自己),若不存在,则 \(p_{i,j}=-1\)。

我们考虑加速刚才朴素算法的过程。

  • 把两个节点跳到同一高度

对于两个节点 \(x,y\),设他们的深度分别为 \(d_x,d_y\) 且 \(d_x \le d_y\),则我们需要把 \(y\) 变成 \(y\) 的第 \(d_x-d_y\) 级祖先,设 \(num=d_x-d_y\)。我们可以倍增地往上跳,从大到小枚举 2 的幂,若当前 2 的幂 \(2^j\) 满足 \(2^j\le num\),就把 \(y\) 更新为 \(p_{y,j}\),然后 \(num\) 减去 \(2^j\) 即可。这样的时间复杂度是 \(O(\log num)\) 的,若一棵树深度为 \(n\),则 \(O(\log num)\) 最坏为 \(O(\log n)\)。

  • 一起往上跳

还是同样的思想,我们依然是从大到小去枚举 2 的幂,对于 \(2^j\) 来说,如果 \(p_{x,j} \not= p_{y,j}\),我们就把 \(x\) 变为 \(p_{x,j}\),\(y\) 变为 \(p_{y,j}\)。最后 \(x,y\) 并不是答案,而是 \(lca(x,y)\) 的两个子节点,所以 \(p_{x,0}\)(或 \(p_{y,0}\))才是答案。这样的时间复杂度也是 \(O(\log n)\) 的。

所以倍增对于每次询问的时间复杂度是 \(O(\log n)\) 的,很明显是快了很多。

实现

  • 初始化

我们先一遍深搜求出每个结点的层级,父节点,然后对于 \(p_{i,j}\),我们可以这样递推:\(p_{i,j}=p_{p_{i,j-1},j-1}\),时间复杂度为 \(O(n \log n)\)。

void dfs(int x, int pr, int lv) {
	lvl[x] = lv, p[x][0] = pr;
	for (int i = 0; i < (int)e[x].size(); i++)
		if (e[x][i].to != pr)	
			dfs(e[x][i].to, x, lv + 1);
}
void initP() {
	for (int i = 0; i <= 20; i++)
		pw[i] = (1 << i);
	for (int j = 1; pw[j] <= n; j++)
		for (int i = 1; i <= n; i++)
			p[i][j] = p[p[i][j - 1]][j - 1];
}
  • 查询第 \(num\) 级祖先

代码很简短,与上面说的一样。

int Qry(int ch, int num) {
	int ans = ch;
	for (int j = 20; j >= 0; j--)
		if (pw[j] <= num)
			ans = p[ans][j], num -= pw[j];
	return ans;
}
  • 查询 \(lca\)

代码不长,思想前面已经说过。

int lca(int x, int y) {
	if (lvl[x] < lvl[y])
		swap(x, y);
	x = Qry(x, lvl[x] - lvl[y]);
	if (x == y)
		return x;
	for (int j = 20; j >= 0; j--)
		if (p[x][j] != p[y][j])
			x = p[x][j], y = p[y][j];
	return p[x][0];
}

一些例题

【模板】最近公共祖先(LCA)

题目链接:【模板】最近公共祖先(LCA)

思路:

模板,记得要加输入输出优化(scanf 和 printf)。

代码:

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 5e5 + 5;
const int MAX_LOG_N = 30;
struct Edge {
	int to, val;
	Edge(int _to, int _val) {
		to = _to, val = _val; 
	}
};
vector<Edge> e[MAXN];
int n, p[MAXN][MAX_LOG_N] = {{0}}, lvl[MAXN] = {0};
int pw[MAX_LOG_N] = {0};
void dfs(int x, int pr, int lv) {
	lvl[x] = lv, p[x][0] = pr;
	for (int i = 0; i < (int)e[x].size(); i++)
		if (e[x][i].to != pr)	
			dfs(e[x][i].to, x, lv + 1);
}
void initP() {
	for (int i = 0; i <= 20; i++)
		pw[i] = (1 << i);
	for (int j = 1; pw[j] <= n; j++)
		for (int i = 1; i <= n; i++)
			p[i][j] = p[p[i][j - 1]][j - 1];
}
int Qry(int ch, int num) {
	int ans = ch;
	for (int j = 20; j >= 0; j--)
		if (pw[j] <= num)
			ans = p[ans][j], num -= pw[j];
	return ans;
}
int lca(int x, int y) {
	if (lvl[x] < lvl[y])
		swap(x, y);
	x = Qry(x, lvl[x] - lvl[y]);
	if (x == y)
		return x;
	for (int j = 20; j >= 0; j--)
		if (p[x][j] != p[y][j])
			x = p[x][j], y = p[y][j];
	return p[x][0];
}
bool chk(int x, int y) {
	return (lvl[x] >= lvl[y] && Qry(x, lvl[x] - lvl[y]) == y);
}  
int m, s;
int main() {
	cin >> n >> m >> s;
	for (int i = 1, u, v; i < n; i++) {
		cin >> u >> v;
		e[u].push_back(Edge(v, 0));
		e[v].push_back(Edge(u, 0));
	}
	dfs(s, 0, 1), initP();
	for (int i = 1, u, v; i <= m; i++) {
		cin >> u >> v;
		cout << lca(u, v) << endl;;
	}
	return 0;
} 

标签:lv,int,笔记,学习,num,LCA,lvl,节点
From: https://www.cnblogs.com/rlc202204/p/17009456.html

相关文章

  • ST表学习笔记
    RMQ问题RMQ(RangeMinimum/MaximumQuery)问题是指多次查询某个范围内的最大最小值(或极值),比如对一个序列多次查询区间的最大最小值。设范围内共有\(n\)个元素,查询\(m\)......
  • 最小生成树学习笔记
    基本概念树定义:树是一个连通且无环的简单无向图。一个\(n\)树有以下三个特点:联通。无环。\(n-1\)条边。上面任意两个条件满足都可以得出这个图是一个......
  • 深度学习中的激励函数
    今天我们会来聊聊现代神经网络中必不可少的一个组成部分,激励函数,activationfunction.非线性方程我们为什么要使用激励函数?用简单的语句来概括.就是因为,现实并......
  • 斯坦福 吴恩达 机器学习课程 视频+讲义+课件等 pdf
    非常优质的机器学习入门课程,课程内容有一定难度,但坚持学完,相信一定会有所收获。 关注公众号:后厂村搬砖工。发送:学习资料汇总即可    ......
  • Spring学习笔记 - 第三章 - AOP与Spring事务
    原文地址:Spring学习笔记-第三章-AOP与Spring事务Spring学习笔记全系列传送门:Spring学习笔记-第一章-IoC(控制反转)、IoC容器、Bean的实例化与生命周期、DI(依......
  • SSM框架视频笔记
    Date:2022-12-2800:35:29【尚硅谷】SSM框架全套教程,MyBatis+Spring+SpringMVC+SSM整合一套通关半夜学习的我像个小丑......
  • MMClassification 实践笔记
    1.配置环境参考文档:https://mmclassification.readthedocs.io/zh_CN/dev-1.x/get_started.htmlgitclone-b1.xhttps://github.com/open-mmlab/mmclassification.git......
  • Python学习笔记--高阶技巧
    闭包(避免全局变量被修改的风险)函数的嵌套的利用若是只是调用到外部函数的值,只需要用到函数的嵌套,具体实现如下:若是要对外部函数的值进行修改,需要用到nonlocal关键字,具......
  • Python学习笔记--数据输出
    数据输出输出为Python对象collect算子具体实现:reduce算子具体实现:take算子具体实现:count算子具体实现:输出到文件中saveAsTextFile算子具体实现:起初......
  • Spring IOC官方文档学习笔记(五)之bean的作用域
    1.Bean的作用域(1)Bean的作用域即Bean实例的作用范围,Spring支持6种bean的作用域,其中4种只能在web环境中使用,具体如下作用域描述singleton默认作用域,采用单例......