当前位置: 首页 > news >正文

【算法】RMQ LCA 讲课杂记

4月4日,应学弟要求去了次学校给小同学们讲了一堂课,其实讲的挺内容挺杂的,但是目的是引出LCA算法。

现在整理一下当天讲课的主要内容:

开始并没有直接引出LCA问题,而是讲了RMQ(Range Minimum/Maximum Query)问题。

RMQ指的是对于给定的一个数组,每一次询问一个区间[L,R]中数字的最小或最大值,一个很经典的问题。

一开始没有学生知道如何求解此问题。我将问题弱化,每一次询问[1,R]中数字的最值,有学生打出了求一个min/max数组即可,此为正解。

这是一个相当正确的做法,有许多学过线段树的同学在碰到这个弱化的问题时也会不假思索的无脑线段树,将简单的问题复杂化。学生能直接答出min/max数组,我认为也是因为他们现在会的算法还太少,基本没有干扰项。

我接下来介绍了ST表(Sparse Table)求解RMQ。

如果我们现在有一个二维数组f[i][j]表示从第i个数开始包括自己向后总共2^j个数字中的最值,如何求解RMQ?

底下有学生给了一种我觉得挺不错的思路:求出L与R的范围D,将D作二进制分解,则可以在log(D)的复杂度呢,通过若干个f数组中的值求解得出L与R之间数字的最值。

这个思路相当nice,但是我说,其实还是不够优秀,因为事实上只要至多2个数字便可以得出结果了。

如果D是一个2的幂,则只f[L][log2(D)]直接就是答案,否则答案为min/max(f[L][(int)log2(D)][R-2^((int)log2(D))+1][(int)log2(D)]),看上去有些复杂,其实写在代码中比较方便,含义就是求出最大的k使得2^k小于D,接着用左边和右边2段的f值一定能完整覆盖到整个范围区间。

实际上,上述两种情况可以归一化为第二种计算方式。

 

接下来的问题就是如何求解f数组。其实ST表本质上是一种动态规划的思想,首先初始化所有的f[i][0]为a[i],也即原来的值,接下去从1开始向上循环,直到超过log(n)。f[i][j]=min/max(f[i][j-1],f[i+2^(j-1)][j-1])

至此ST表算法讲授完毕。另外一点想提的是,很多人会把RMQ等同于ST表,这是一种误会。RMQ并不是一种算法,是区间最值问题,本质上是一类问题。RMQ当然也可以用线段树解决。甚至brute force也可以称为RMQ的一种算法。

 

接下去,我讲了有向图深度优先遍历中的四类边,本文不作展开。

 

最后,我引出了LCA问题。底下的学生没有人知道不用暴力怎么做。但是有的学生的暴力思路还是有些interesting的,例如先遍历一遍树,当发现第一个点后,在回溯时,标记走过的点的值+1,。再遍历一次树,当发现第二个点后,在回溯时,标记走过的点的值+1,第一次发现有个点值为2,则说明是两个点的最近公共祖先。

我觉得这个思路还是有点意思的,因为比较反常规。

 

我主要讲了三种求解LCA问题的算法。

第一种是将其归约至RMQ问题。如何规约呢?在遍历到一个节点与回溯到一个节点时,打时间戳,并记录这个时间戳对应的节点。则LCA问题可以规约为对应2个点第一次被遍历到的时间戳之间所有所有时间戳,哪个时间戳对应的节点的深度最小。

听起来有些复杂,其实想想就是那么回事情。

在问到如何将LCA规约至RMQ时,有一个学生意外地联想到了第二种方法,不过他没有完整地想出来。

 

第二种算法叫树上倍增,与ST表有异曲同工之处,f[i][j]表示节点i的第2^j个祖先。

则求解LCA的算法流程大致是这样的,两个点u和v,不妨假设u的深度大于等于v的深度,则让u向上到两点处于同一个深度。如果两个点相同,则直接返回答案。否则从大到小枚举2的幂,看是否两个点向上那么多个祖先是相同的,如果相同,则认为走过头了,continue掉。否则将其置为新的祖先点。最后2个点再向上走一次,也即他们的父亲节点就是LCA。这其中让u向上走到与v相同高度,用的方法与上文RMQ问题中学生提到的二进制分解是一个思路,即将深度差作二进制分解,向上跳log(d)次。

 

第三种算法是Tarjan发明的一种基于并查集的离线算法。

所谓离线即先一次性将所有的询问保存,对树做深度优先遍历,每一次遍历完之后,将子树对应的集合合并至当前节点。递归完所有子节点后,遍历所有与当前点相关的询问,如果另外一个点已经被访问过了,则这个询问的答案为另一个点所在集合的代表元。

 

最后我当时提了一下,没有深入讲,是用树链剖分求解LCA问题。有点杀鸡用宰牛刀的感觉了。

 

转载于:https://www.cnblogs.com/micrari/p/5385152.html

相关文章:

  • javascript高级程序设计
  • Objective—C中的排序及Compare陷阱
  • 《Struts2.x权威指南》学习笔记2
  • 【作业3】关于C语言的问卷调查
  • 控制台手动编译Qt5程序
  • 创建NetWorkDataset---Shapefile篇
  • 获取验证码按钮点击后,一分钟内不可继续点击
  • Delphi Canvas的FillRect(const Rect: TRect) 函数的作用
  • B+/-Tree原理及mysql的索引分析
  • 关闭Rootless机制
  • 图像缩放算法
  • Effective C++笔记(三):资源管理
  • IOS开发之远程推送
  • Set的用法
  • 安卓手持智能POS端上能扫描开单的软件-店面销售开单系统
  • 【Leetcode】101. 对称二叉树
  • [case10]使用RSQL实现端到端的动态查询
  • 【许晓笛】 EOS 智能合约案例解析(3)
  • 〔开发系列〕一次关于小程序开发的深度总结
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • Hexo+码云+git快速搭建免费的静态Blog
  • JavaScript 奇技淫巧
  • JS数组方法汇总
  • LeetCode541. Reverse String II -- 按步长反转字符串
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • Mithril.js 入门介绍
  • React-Native - 收藏集 - 掘金
  • Vultr 教程目录
  • 湖南卫视:中国白领因网络偷菜成当代最寂寞的人?
  • 技术攻略】php设计模式(一):简介及创建型模式
  • 判断客户端类型,Android,iOS,PC
  • 扑朔迷离的属性和特性【彻底弄清】
  • 听说你叫Java(二)–Servlet请求
  • 小程序、APP Store 需要的 SSL 证书是个什么东西?
  • 一些css基础学习笔记
  • 译有关态射的一切
  • 终端用户监控:真实用户监控还是模拟监控?
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • #define与typedef区别
  • (八)c52学习之旅-中断实验
  • (八)Docker网络跨主机通讯vxlan和vlan
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (每日持续更新)jdk api之FileReader基础、应用、实战
  • (四)Controller接口控制器详解(三)
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • (四)事件系统
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (转) 深度模型优化性能 调参
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • .NET Core中的去虚
  • .net framework profiles /.net framework 配置
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .net FrameWork简介,数组,枚举