[算法学习]——通过RMQ与dfs序实现O(1)求LCA(含封装板子)

每周五篇博客:(3/5)

碎碎念

其实不是我想多水一篇博客,本来这篇是欧拉序的博客,结果dfs序也是可以O1求lca的,而且常数更优,结果就变成这样了。。。

前置知识

[算法学习]——dfs序

思想

分类讨论

对于查询的两个节点 u , v u, v u,v ,称两个节点的最近公共祖先为 l c a lca lca ,首先我们先确保 d f n u ≤ d f n v dfn_u \le dfn_v dfnudfnv (这里的 d f n u dfn_u dfnu 表示 u u u 的dfs序,也是前置知识中的 l u l_u lu),如果 d f n u > d f n v dfn_u > dfn_v dfnu>dfnv 的话我们swap一下就可以

如果 u u u v v v 的祖先节点的话,那么 l c a lca lca 一定是 u u u ,只需要判断下是否满足 d f n u ≤ d f n v dfn_u \le dfn_v dfnudfnv(相当于 l u ≤ l v l_u \le l_v lulv) 并且 r u ≥ r v r_u \ge r_v rurv 即可

如果 u u u 不是 v v v 的祖先节点,并且我们保证了 d f n u ≤ d f n v dfn_u \le dfn_v dfnudfnv 所以 u , v u, v u,v 一定在不同的子树中,例如

image-20250502165701272

蓝色圈内的节点其实是dfs序处于 [ d f n u , d f n v ] [dfn_u, dfn_v] [dfnu,dfnv] 内的所有节点

这于欧拉序不同的地方在于这个区间内并不包含 l c a lca lca 节点,但是却包含了 l c a lca lca 的儿子节点,事实上在dfs序处于区间 [ d f n u , d f n v ] [dfn_u, dfn_v] [dfnu,dfnv] 中至少存在一个 l c a lca lca 的儿子节点。我们设这个 l c a lca lca 的儿子节点为 w w w ,由于 u , v u, v u,v 不在同一子树中,在dfs遍历完 u u u 子树后会返回到 l c a lca lca 节点接着去遍历 u u u 子树之后的其他子树,当遍历到 v v v 节点所在的子树时,这个子树的顶点便是 w w w v , w v, w v,w 有可能是同一个节点),因为遍历 w w w 在遍历 v v v,所以有 d f n w ≤ d f n v dfn_w \le dfn_v dfnwdfnv,那么 d f n w dfn_w dfnw 也就处于区间 [ d f n u , d f n v ] [dfn_u, dfn_v] [dfnu,dfnv]

而这个 w w w 一定是区间 [ d f n u , d f n v ] [dfn_u, dfn_v] [dfnu,dfnv] 中深度最小的节点,因为其父亲节点是 l c a lca lca ,而且区间 [ d f n u , d f n v ] [dfn_u, dfn_v] [dfnu,dfnv] 只会包含 l c a lca lca 子树内部的节点(不包含 l c a lca lca),所以 l c a lca lca 的儿子节点就是深度最小的节点

所以我们只需要查询到区间 [ d f n u , d f n v ] [dfn_u, dfn_v] [dfnu,dfnv] 最小的节点的父亲,就可以找到 l c a lca lca

关于区间查询,因为是静态查询并不涉及到修改操作,所以我们可以使用RMQ算法来实现 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的预处理以及 O ( 1 ) O(1) O(1) 的单次查询

而关于维护RMQ的预处理,我们定义 m i n j , i min_{j, i} minj,i 表示dfs序中 [ i , i + 2 j ] [i, i + 2^j] [i,i+2j] 区间内深度最浅的节点的编号,所以初始化有 m i n 0 , i = i d i min_{0, i} = id_i min0,i=idi (这里的 i d i id_i idi 表示dfs序为 i i i 所对应的节点的编号),关于维护 m i n j , i min_{j,i} minj,i 数组,我们比较两个区间中深度最浅的节点哪个区间更浅就可以了,这里可以单独写一个比较函数,关于查询和普通的RMQ一样,不过需要配合刚刚的比较函数

代码

例题:P3379 【模板】最近公共祖先(LCA)

int min[21][N];std::vector<int> go[N];
int l[N], r[N], id[N], tot, dep[N], f[N];
void dfs(int u, int fa) {f[u] = fa;l[u] = ++ tot;id[tot] = u;dep[u] = dep[fa] + 1;for (auto v : go[u]) {if (v == fa) continue;dfs(v, u);}r[u] = tot;
}int update(int x, int y) {if (dep[x] < dep[y]) return x;return y;
}void rmq(int n) {for (int i = 1; i <= n; i ++) min[0][i] = id[i];for (int j = 1; j <= std::__lg(n); j ++)for (int i = 1; i + (1 << j) - 1 <= n; i ++)min[j][i] = update(min[j - 1][i], min[j - 1][i + (1 << (j - 1))]);
}int lca(int u, int v) {if (l[u] > l[v]) std::swap(u, v);if (l[u] <= l[v] && r[u] >= r[v]) return u;u = l[u], v = l[v];int k = std::__lg(v - u + 1);return f[update(min[k][u], min[k][v - (1 << k) + 1])];
}void solve() {int n, q, root;std::cin >> n >> q >> root;for (int i = 1; i < n; i ++) {int u, v;std::cin >> u >> v;go[u].push_back(v);go[v].push_back(u);}dfs(root, 0);rmq(n);while (q --) {int u, v;std::cin >> u >> v;std::cout << lca(u, v) << "\n";}
}

板子

其实这个板子是我AC后让gpt给我封装的

struct LCA {int n, LOG;std::vector<int> l, r, id, dep, parent, lg;std::vector<std::vector<int>> st;const std::vector<std::vector<int>>& adj;int tot = 0;// 构造函数:传入节点数 n(假设节点编号 1..n)、邻接表 adj、根节点 rootLCA(int _n, const std::vector<std::vector<int>>& _adj, int root): n(_n), adj(_adj){LOG = 32 - __builtin_clz(n);  // ⌊log2(n)⌋ 的上界l.assign(n+1, 0);r.assign(n+1, 0);id.assign(n+1, 0);dep.assign(n+1, 0);parent.assign(n+1, 0);lg.assign(n+2, 0);// 预处理对数for (int i = 2; i <= n; i++)lg[i] = lg[i>>1] + 1;// 1) 建立 dfs 序,记录 l[u], r[u], id[]dfs(root, 0);// 2) 构建 ST 表用于 RMQst.assign(LOG+1, std::vector<int>(n+2));// 第一层直接存序列上的节点编号for (int i = 1; i <= n; i++)st[0][i] = id[i];// 其余层for (int j = 1; j <= LOG; j++) {for (int i = 1; i + (1<<j) - 1 <= n; i++) {int x = st[j-1][i];int y = st[j-1][i + (1<<(j-1))];// 取深度更小(即在树上更靠近根)的那个st[j][i] = (dep[x] < dep[y] ? x : y);}}}// 返回节点 u 在序列中的位置 l[u], 以及构造 parent, depvoid dfs(int u, int p) {parent[u] = p;dep[u] = dep[p] + 1;l[u] = ++tot;id[tot] = u;for (int v : adj[u]) {if (v == p) continue;dfs(v, u);}r[u] = tot;}// O(1) 查询 LCAint lca(int u, int v) const {// 如果 u 是 v 的祖先,直接返回 u;反之同理if (l[u] <= l[v] && r[u] >= r[v]) return u;if (l[v] <= l[u] && r[v] >= r[u]) return v;// 保证 l[u] < l[v]if (l[u] > l[v]) std::swap(u, v);// 在序列 [l[u]..l[v]] 上做 RMQ,找到深度最小的节点 xint L = l[u], R = l[v];int k = lg[R-L+1];int x1 = st[k][L], x2 = st[k][R - (1<<k) + 1];int x  = (dep[x1] < dep[x2] ? x1 : x2);// 这个 x 一定是 u 到 v 路径上,且最靠近根的那个孩子节点,// 它的 parent 就是 LCAreturn parent[x];}
};

使用方法:

void solve() {int n, q, root;std::cin >> n >> q >> root;std::vector go(n + 1, std::vector<int>());for (int i = 1; i < n; i ++) {int u, v;std::cin >> u >> v;go[u].push_back(v);go[v].push_back(u);}LCA lca(n, go, root);while (q --) {int u, v;std::cin >> u >> v;std::cout << lca.lca(u, v) << "\n";}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/81417.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

spark local模式

Spark Local 模式是一种在单台机器上运行 Spark 应用程序的模式&#xff0c;无需搭建分布式集群&#xff0c;适合开发调试、学习以及运行小规模数据处理任务。以下为你详细介绍该模式&#xff1a; 特点 简易性&#xff1a;无需额外配置分布式集群&#xff0c;在单机上就能快速…

用 RxSwift 实现 UITableView 的响应式绑定(超实用示例)

目录 前言 一、环境准备 1.安装 RxSwift 和 RxCocoa 2.导入模块 二、实现一个简单的UITableView 1.实现一个简单的 UITableView 1.实现步骤 1.我们声明一个ViewModel 2.ViewModel和UITableView 绑定 2.实现 UITableView 的代理方法 三、处理点击事件 前言 在 iOS 开发…

【C++】通过红黑树封装map和set

前言&#xff1a; 通过之前的学习&#xff0c;我们已经学会了红黑树和map、set。这次我们要实现自己的map和set&#xff0c;对&#xff0c;使用红黑树进行封装&#xff01; 当然&#xff0c;红黑树内容这里就不在赘述&#xff0c;我们会复用红黑树的代码&#xff0c;所以先将…

非凸科技受邀出席AI SPARK活动,共探生成式AI驱动金融新生态

4月19日&#xff0c;由AI SPARK社区主办的“生成式AI创新与应用构建”主题沙龙在北京举行。活动聚焦生成式AI的技术突破与产业融合&#xff0c;围绕大模型优化、多模态应用、存内计算等前沿议题展开深度探讨。非凸科技受邀出席并发表主题演讲&#xff0c;深入解析金融垂直大模型…

【Java IO流】IO流详解

参考笔记&#xff1a;【Java基础-3】吃透Java IO&#xff1a;字节流、字符流、缓冲流_javaio-CSDN博客 目录 1.IO流简介 1.1 什么是IO流&#xff1f; 1.2 IO流的分类 1.3 字符流和字节流的其他区别 1.4 Java IO流体系图 2.字符编码详解 3. Java的char类型与 Unicode、U…

驱动开发系列56 - Linux Graphics QXL显卡驱动代码分析(三)显示模式设置

一:概述 如之前介绍,在qxl_pci_probe 中会调用 qxl_modeset_init 来初始化屏幕分辨率和刷新率,本文详细看下 qxl_modeset_init 的实现过程。即QXL设备的显示模式设置,是如何配置CRTC,Encoder,Connector 的以及创建和更新帧缓冲区的。 二:qxl_modeset_init 分析 in…

Vue3开发常见性能问题知多少

文章目录 1 常见性能优化瓶颈及原因1.1 响应式数据的过度使用1.2 虚拟 DOM 的频繁更新1.3 组件渲染的冗余1.4 大列表渲染的性能问题1.5 计算属性和侦听器的滥用1.6 事件处理函数的频繁绑定1.7 异步组件的加载性能2 解决方案与优化技巧2.1 合理使用响应式数据2.2 优化虚拟 DOM 更…

Rust Ubuntu下编译生成环境win程序踩坑指南

前言&#xff1a; 1&#xff0c;公司要给一线搞一个升级程序&#xff0c;需要在win下跑。 之前都是找开发总监帮忙&#xff0c;但是他最近比较忙。就让我自己搞。有了下文.。说来惭愧&#xff0c;之前写过一篇ubuntu下编译windows的文章。里面的demo就一句话 fuck world。依赖…

openharmony 4.1 运行busybox工具包(保姆教程)

1.下载 链接&#xff1a;Index of /downloads/binaries 进入其中后&#xff0c;找到 挑选适合你系统架构的版本&#xff0c;例如我这边是 https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-armv7r 右键复制链接 打开迅雷&#xff0c;直接粘…

算法四 习题 1.3

数组实现栈 #include <iostream> #include <vector> #include <stdexcept> using namespace std;class MyStack { private:vector<int> data; // 用于存储栈元素的数组public:// 构造函数MyStack() {}// 入栈操作void push(int val) {data.push_back…

GD32F407单片机开发入门(十七)内部RTC实时时钟及实战含源码

文章目录 一.概要二.RTC基本特点三.GD32单片机RTC内部结构图四.配置一个RTC走秒例程五.工程源代码下载六.小结 一.概要 RTC&#xff08;Real-Time Clock&#xff09;是一种用于追踪和记录实际时间的时钟系统。RTC模块提供了一个包含日期&#xff08;年/月/日&#xff09;和时间…

新能源汽车运动控制器核心芯片选型与优化:MCU、DCDC与CANFD协同设计

摘要&#xff1a;随着新能源汽车产业的迅猛发展&#xff0c;汽车运动控制器的性能和可靠性面临着更高的要求。本文深入探讨了新能源汽车运动控制器中MCU&#xff08;微控制单元&#xff09;、DCDC电源管理芯片和CANFD总线通信芯片的选型要点、优化策略及其协同设计方案。通过综…

2.maven 手动安装 jar包

1.背景 有的时候&#xff0c;maven仓库无法下载&#xff0c;可以手动安装。本文以pentaho-aggdesigner-algorithm-5.1.5-jhyde.jar为例。 2.预先准备 下载文件到本地指定位置。 2.1.安装pom mvn install:install-file \-Dfile/home/wind/tmp/pentaho-aggdesigner-5.1.5-jh…

OpenCV 图形API(75)图像与通道拼接函数-----将 4 个单通道图像矩阵 (GMat) 合并为一个 4 通道的多通道图像矩阵函数merge4()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 由4个单通道矩阵创建一个4通道矩阵。 该函数将多个矩阵合并为一个单一的多通道矩阵。也就是说&#xff0c;输出矩阵的每一个元素都是输入矩阵对…

AI日报 · 2025年05月02日 | 再见GPT-4!OpenAI CEO 确认 GPT-4 已从 ChatGPT 界面正式移除

1、OpenAI CEO 确认 GPT-4 已从 ChatGPT 界面正式移除 在处理 GPT-4o 更新问题的同时&#xff0c;OpenAI CEO Sam Altman 于 5 月 1 日在 X 平台发文&#xff0c;正式确认初代 GPT-4 模型已从 ChatGPT 主用户界面中移除。此举遵循了 OpenAI 此前公布的计划&#xff0c;即在 4 …

patch命令在代码管理中的应用

patch 是一个用于将差异文件&#xff08;补丁&#xff09;应用到源代码的工具&#xff0c;常用于修复 bug、添加功能或调整代码结构。在您提供的代码中&#xff0c;patch 命令通过一系列补丁文件&#xff08;.patch&#xff09;修改了 open-amp 库的源代码。 patch 命令的核心作…

spring-ai集成langfuse

1、pom文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.…

PyTorch 与 TensorFlow:深度学习框架的深度剖析与实战对比

PyTorch 与 TensorFlow&#xff1a;深度学习框架的深度剖析与实战对比 摘要 &#xff1a;本文深入对比 PyTorch 与 TensorFlow 两大深度学习框架&#xff0c;从核心架构、优缺点、适用场景等多维度剖析&#xff0c;结合实例讲解&#xff0c;帮助开发者清晰理解两者特性&#x…

如何配置NGINX作为反向代理服务器来缓存后端服务的响应?

大家好&#xff0c;我是锋哥。今天分享关于【如何配置NGINX作为反向代理服务器来缓存后端服务的响应&#xff1f;】面试题。希望对大家有帮助&#xff1b; 如何配置NGINX作为反向代理服务器来缓存后端服务的响应&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源…

DiT:文档图像Transformer 的自监督预训练

摘要 图像transformer&#xff08;Image Transformer&#xff09;最近在自然图像理解方面取得了显著进展&#xff0c; 无论是使用监督&#xff08;ViT、DeiT等&#xff09;还是自监督&#xff08;BEiT、MAE等&#xff09;预训练技术。在本文中&#xff0c;我们提出了DiT&#…