【C++】倍增LCA详解 + P3379 最近公共祖先题解

文章目录

    • 1.暴力做法
    • 2.倍增做法
      • 问题1
      • 问题2
    • 总结
    • Code
    • End

这道题是一道求树上最近公共祖先的模板题。

1.暴力做法

我们先思考O(n)的暴力做法:(这里的n是指树的最大深度,也可以近似于节点个数)

我们假设我们要求的是lca(u,v),那么可以考虑让u,v首先跳到同一深度,然后一同向上走,每次走一步额,直到节点重合为止
可以知道我们一定会找到lca,因为如果前面所有的都找不到,那也一定会汇聚在根节点

这样是线性复杂度,在多次查询的题目里会有O(n^2)的复杂度,显然还不够。

我们考虑优化:使用倍增算法。

2.倍增做法

倍增,其底层原理是根据二进制优化枚举,因为一个数一定可以根据二进制拆成2的幂次相加的形式

所以,我们只要每次都往上跳2的幂次步,那复杂不就优化到O(logn)了吗?

这显然可行,我们先让u,v跳到同一深度:预处理深度差d,如果d的第i位二进制为1,就往上跳2^i步,
然后再两个一同往上跳,同上,每次跳2的幂次步即可

这时候,又出现了两个问题:

  1. 如何在O(1)的复杂度跳到当前节点的第2^i次方个节点?
  2. 我们并不知道lca(u,v)和u,v的深度差,那如何根据二进制往上跳呢?

问题1

可以通过O(n)的预处理实现。设f[u][i]表示节点u向上跳2^i步到的节点

我们对整棵树跑dfs,同时,对于节点u,记录u的深度dep[u],
转移f数组:f[u][i] = f[f[u][i-1]][i-1] 1<=i<=log2(dep[u]) ……对节点u上方的所有节点都预处理
(注意取值范围从1开始,因为从0开始的话,i-1可能越界。处理方法只要在递归u的父亲节点k时把f[u][0]=k即可)

这个转移十分巧妙,原理: 2 i − 1 + 2 i − 1 = 2 i 2^{i-1}+2^{i-1}=2^i 2i1+2i1=2i
节点u向上跳 2 i 2^i 2i 步=节点u向上跳 2 i − 1 2^{i-1} 2i1 步的节点v 向上跳 2 i − 1 2^{i-1} 2i1 步的位置
因为我们从上向下遍历树,同时枚举i也是从小到大枚举,所以不会出现转移的状态没有的情况。

问题2

首先思路1:发现这个问题具有单调性,可以二分
因为,如果lca(u,v)=k,则k的所有祖先也都是(u,v)的公共祖先。所以可以二分向上跳的高度
但这样复杂度是 ( log ⁡ n ) 2 (\log n)^2 (logn)2 的,我们还可以优化成单log

思路2:
我们可以直接从大到小枚举log2(dep[u])次(注意此时dep[u]==dep[v]),只要f[u][i]!=f[v][i]就继续向上跳,这样一定能到达

原因:从大到小凑一定可以凑出所有的数,因为对于偶数可以拆成若干次 2 i 2^i 2i 到达;奇数可以先走若干次 2 i 2^i 2i ,再用一个 2 0 = 1 2^0=1 20=1 到达
例如u,v和lca的深度差为 5 = 2 0 + 2 1 + 2 1 = 2 0 + 2 2 5=2^0+2^1+2^1=2^0+2^2 5=20+21+21=20+22,从小到大根本不知道这个 2 1 2^1 21 要来几次;但从大到小来可以直接先用 2 2 2^2 22,再用 2 1 2^1 21

至此,倍增思想和LCA的完美结合结束!

总结

要点:

  1. 倍增本质上也是一种dp,常用状态:f[i][j]表示第i个位置后2^j个位置的……,
    常用转移:f[i][j]=f[f[i-1][j 不定]][j-1]

  2. 上面提到倍增也可以用二分来实现,因为二分和倍增可以看作两个相反的操作。

    二分是从大区间不断折半(21,22,…2^k)到小区间来实现查找,而倍增是从小区间倍到大区间。

    所以很多二分的题目也可以用倍增,只要构建好递推关系即可

Code

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 7;vector <int> T[maxn];
int f[maxn][25]; // 2^20≈1e6
int dep[maxn];void dfs(int u){ // f[u][0]就相当于父亲节点,即u往上2^0=1个节点dep[u] = dep[f[u][0]] + 1;for(int i = 1; i <= log2(dep[u]); i ++){ // 从1开始,因为f[u][0]在本次递归开始之前就确定了,同时i=0时i-1会越界f[u][i] = f[f[u][i - 1]][i - 1];}for(int v : T[u]){if(v == f[u][0]) continue;f[v][0] = u; // 提前处理一下dfs(v);}
}int lca(int u, int v){// 让u为较深的那个点,便于计算if(dep[u] < dep[v]) swap(u, v);// 让u跳到与v相同的高度int d = dep[u] - dep[v];for(int i = 0; i <= log2(d); i ++){ // 枚举每一个二进制位if(d >> i & 1){u = f[u][i];}}if(u == v) return u; // 特判此时已经相等的情况s// 同时向上跳,直到相等for(int i = log2(dep[u]); i >= 0; i --){ // 注意倒着枚举if(f[u][i] != f[v][i]){u = f[u][i], v = f[v][i];}}return f[u][0];
}void solve()
{int n, m, s; cin >> n >> m >> s;for(int x, y, i = 1; i <= n - 1; i ++){cin >> x >> y;T[x].push_back(y);T[y].push_back(x);}// 预处理节点深度、倍增数组dfs(s);// 处理询问for(int a, b, i = 1; i <= m; i ++){cin >> a >> b;cout << lca(a, b) << '\n';}
}signed main()
{ios :: sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);solve();return 0;
}

End

经过这么详细的讲解,大家一定对倍增LCA有了些许了解吧。

这里是 YLCHUP,谢谢大家,拜拜ヾ(•ω•`)o

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

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

相关文章

Web Components标准化与浏览器兼容性

Web Components是一套W3C标准&#xff0c;旨在提供一种构建可重用、封装良好的Web界面组件的方法。这套标准包括四个主要部分&#xff1a;Custom Elements、Shadow DOM、HTML Templates和HTML Imports&#xff08;后者已被废弃&#xff0c;通常被ES6 Modules替代&#xff09;。…

性能调优本质:如何精准定位瓶颈并实现系统极致优化

目录 先入为主的反例 性能调优的本质 性能调优实操案例 性能调优相关文章 先入为主的反例 在典型的 ETL 场景中,我们经常需要对数据进行各式各样的转换,有的时候,因为业务需求太复杂,我们往往还需要自定义 UDF(User Defined Functions)来实现特定的转换逻辑。 但是…

spring IOC DI -- IOC详解

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 今天你敲代码了吗 文章目录 4.2 Ioc 详解4.2.1 Bean的存储Controller(控制器存储)Service (服务存储)Repository(仓库存储)Component(组件存储)Configuration(配置存储) 4.2.2 为什么需要这么多类注解?4.2.3方法…

追问试面试系列:线程池

hi 欢迎来到线程池系列,线程池在面试中出现的频率非常高。 先看面试题 关于线程池的追问试一共整理22问: ● 面试官:为什么要用线程池 ● 面试官:那怎么创建线程池? ● 面试官:为什么不推荐使用Executors类创建线程池? ● 面试官:ThreadPoolExecutor 有哪些常用的方…

Vue如何让用户通过a链接点击下载一个excel文档

在Vue中&#xff0c;通过<a>标签让用户点击下载Excel文档&#xff0c;通常需要确保服务器支持直接下载该文件&#xff0c;并且你有一个可以直接访问该文件的URL。以下是一些步骤和示例&#xff0c;展示如何在Vue应用中实现这一功能。 1. 服务器端支持 首先&#xff0c;…

AI学习指南机器学习篇- 标签传播算法的数学基础

AI学习指南机器学习篇- 标签传播算法的数学基础 引言 标签传播算法是一种常用的无监督学习算法&#xff0c;广泛应用于社区检测、聚类分析、图像分割等领域。本文将介绍标签传播算法背后的数学理论&#xff0c;包括图论、概率图模型等&#xff0c;同时探讨算法的收敛性和稳定…

面试重点---快速排序

快排单趟 快速排序是我们面试中的重点&#xff0c;这个知识点也很抽象&#xff0c;需要我们很好的掌握&#xff0c;而且快速排序的代码也是非常重要&#xff0c;需要我们懂了还不行&#xff0c;必须要手撕代码&#xff0c;学的透彻。 在研究快速排序之前&#xff0c;我们首先…

depcheck 前端依赖检查

介绍 depcheck 是一款用于检测项目中 未使用依赖项 的工具。 depcheck 通过扫描项目文件&#xff0c;帮助你找出未被引用的依赖&#xff0c;从而优化项目。 优势&#xff1a; 简单易用: 仅需几个简单的命令&#xff0c;就能够扫描并列出未使用的依赖项&#xff0c;让你快速了…

GeneCompass:跨物种大模型用于破解基因调控机理

GeneCompass是第一个基于知识的跨物种基础模型&#xff0c;该模型预先训练了来自人类和小鼠的超过1.2亿个单细胞转录组。在预训练过程中&#xff0c;GeneCompass有效整合了四种生物先验知识&#xff0c;以自监督的方式增强了对基因调控机制的理解。对多个下游任务进行微调&…

PlatformIO+ESP32S3学习:通过WIFI与和风天气API获取指定地点的天气情况并显示

1. 硬件准备 你只需要有一个ESP32S3开发板。我目前使用的是&#xff1a; 购买地址&#xff1a;立创ESP32S3R8N8 开发板 2. 和风天气API 2.1. 和风天气介绍 和⻛天气是中国领先的气象科技服务商、国家高新技术 企业&#xff0c;致力于运用先进气象模型结合大数据、人工智能 技术…

成为git砖家(2): gitk 介绍

大家好&#xff0c;我是白鱼。这篇我们介绍 gitk。 gitk 和 fork 界面对比 当我们在 macOS 上执行 brew install git 后&#xff0c; 得到了 git 命令行工具。 然而这条命令并不会安装 gitk. gitk 是 git 自带的图形化界面工具&#xff0c;也可以称为“穷人版 fork”&#xf…

美国演员工会和电视广播艺人工会针对电子游戏发行商的罢工于 7 月 26 日举行

美国演员工会&#xff08;SAG-AFTRA&#xff09;正在对电子游戏发行商进行罢工&#xff0c;以保护演员不被人工智能所利用。经过一年半的谈判&#xff0c;双方仍未达成协议。该工会希望确保人工智能不会被用作利用大型游戏中演员的手段。 他们在网站上声明&#xff0c;“从事电…

JavaScript实战 - 用Canvas画一个心形

作者&#xff1a;逍遥Sean 简介&#xff1a;一个主修Java的Web网站\游戏服务器后端开发者 主页&#xff1a;https://blog.csdn.net/Ureliable 觉得博主文章不错的话&#xff0c;可以三连支持一下~ 如有疑问或建议&#xff0c;请私信或评论留言&#xff01; 前言&#xff1a; 如…

搭建自己的金融数据源和量化分析平台(三):读取深交所股票列表

深交所的股票信息读取比较简单&#xff1a; 看上图&#xff0c;爬虫读取到下载按钮的链接之后发起请求&#xff0c;得到XLS文件后直接解析就可以了。 这里放出深交所爬虫模块的代码&#xff1a; # -*- coding: utf-8 -*- # 深圳交易所爬虫 import osimport pandas as pd imp…

Linux 命令提示符 PS1设置

在 Linux 中&#xff0c;命令行提示符&#xff08;prompt&#xff09;中的 $ 和 # 是 shell 提供的默认字符&#xff0c;用于区分普通用户和超级用户&#xff08;root&#xff09;。$ 通常用于普通用户会话&#xff0c;而 # 用于超级用户会话。这些字符是提示符的一部分&#x…

fastapi教程(四):做出响应

请求体现的是后端的数据服务能力&#xff0c;而响应体现的是后端向前端的数据展示能力。 一&#xff0c;一个完整的web响应应该包含哪些东西 一个完整的 Web 响应通常包含以下几个主要部分&#xff1a; 1. 状态行- HTTP 版本- 状态码- 状态消息例如&#xff1a;HTTP/1.1 200…

AST反混淆进阶系列|各类简单函数调用还原及对应还原插件参考

关注它&#xff0c;不迷路。 本文章中所有内容仅供学习交流&#xff0c;不可用于任何商业用途和非法用途&#xff0c;否则后果自负&#xff0c;如有侵权&#xff0c;请联系作者立即删除&#xff01;

全开源收银系统源码-支付通道

1.收银系统开发语言 核心开发语言: PHP、HTML5、Dart后台接口: PHP7.3后合管理网站: HTML5vue2.0element-uicssjs线下收银台&#xff08;安卓/PC收银、安卓自助收银&#xff09;: Dart3框架&#xff1a;Flutter 3.19.6助手: uniapp商城: uniapp 2.支付通道 智慧新零售收银系统…

一下午连续故障两次,谁把我们接口堵死了?!

唉。。。 大家好&#xff0c;我是程序员鱼皮。又来跟着鱼皮学习线上事故的处理经验了喔&#xff01; 事故现场 周一下午&#xff0c;我们的 编程导航网站 连续出现了两次故障&#xff0c;每次持续半小时左右&#xff0c;现象是用户无法正常加载网站&#xff0c;一直转圈圈。 …

Vue3响应式高阶用法之`shallowReadonly()`

Vue3响应式高阶用法之shallowReadonly() 在现代前端开发中&#xff0c;Vue3 提供了丰富的响应式 API 来帮助开发者更高效地管理状态和数据。其中&#xff0c;shallowReadonly() 是一个非常有用的工具&#xff0c;适用于需要部分只读状态的场景。本文将详细介绍 shallowReadonl…