当前位置: 首页 > 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端上能扫描开单的软件-店面销售开单系统
  • ES6指北【2】—— 箭头函数
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • javascript从右向左截取指定位数字符的3种方法
  • JavaScript服务器推送技术之 WebSocket
  • js继承的实现方法
  • Mac转Windows的拯救指南
  • MQ框架的比较
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • Vue ES6 Jade Scss Webpack Gulp
  • vue 配置sass、scss全局变量
  • 构建二叉树进行数值数组的去重及优化
  • 解决iview多表头动态更改列元素发生的错误
  • 利用阿里云 OSS 搭建私有 Docker 仓库
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 使用Envoy 作Sidecar Proxy的微服务模式-4.Prometheus的指标收集
  • 手机app有了短信验证码还有没必要有图片验证码?
  • 摩拜创始人胡玮炜也彻底离开了,共享单车行业还有未来吗? ...
  • ​520就是要宠粉,你的心头书我买单
  • ​linux启动进程的方式
  • ​卜东波研究员:高观点下的少儿计算思维
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • $分析了六十多年间100万字的政府工作报告,我看到了这样的变迁
  • ()、[]、{}、(())、[[]]命令替换
  • (C语言)fread与fwrite详解
  • (c语言版)滑动窗口 给定一个字符串,只包含字母和数字,按要求找出字符串中的最长(连续)子串的长度
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第5节(封闭类和Final方法)
  • (ZT)一个美国文科博士的YardLife
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (转)3D模板阴影原理
  • (转载)在C#用WM_COPYDATA消息来实现两个进程之间传递数据
  • ******之网络***——物理***
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • .chm格式文件如何阅读
  • .halo勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .java 指数平滑_转载:二次指数平滑法求预测值的Java代码
  • .net php 通信,flash与asp/php/asp.net通信的方法
  • .Net Remoting(分离服务程序实现) - Part.3
  • .NET 读取 JSON格式的数据
  • .net和jar包windows服务部署