RMQ 和 LCA 问题
# Part 1 RMQ
RMQ,即区间信息维护问题
如最大值,最小值,GCD 等
RMQ 算法实现很多
具体有线段树,树状数组和 ST 表
但综合时间复杂度最好的是 ST 表
查询 O(1),预处理 O(n log n)
ST 表的基础思想是二进制倍增
记录一个 ST[i][j] 数组记录一下从 lable[i] 开始长度为 2^j 区间的值
这个算法有一个很重要的要求
就是维护的信息必须具有可重复性
以区间 max 为例
一段 [1,3] 的区间
其中可以多次对一个元素取 max
显然区间加,乘,异或肯定是不行的
最终查询时只需要取 (int)log(rim-lim) 的二进制长度
(其中 log 可以预处理)
由于他不一定正好是一整段
我们用开头后和末尾前的一段进行合并
即 ans(lim,rim)=solve(ST[lim][log],ST[rim-(1<<log)+1][log])
即 ST 的 O(1) 查询
Question 01 [ACP2151 数列区间最大值]
模板题
Code
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,LIM=21;
int k[N],ST[N][LIM];
int n,Shit[N];
int main(){
int T,lim,rim;
scanf("%d%d",&n,&T);
Shit[1]=0;
for(int i=1;(i<<1)<=n;i++)Shit[i<<1]=Shit[i]+1,Shit[(i<<1)|1]=Shit[i]+1;
for(int i=1;i<=n;i++)scanf("%d",&k[i]),ST[i][0]=k[i];
for(int i=1;i<=20;i++)for(int j=1;j+(1<<i)-1<=n;j++)ST[j][i]=max(ST[j][i-1],ST[j+(1<<(i-1))][i-1]);
for(int i=1;i<=T;i++){
scanf("%d%d",&lim,&rim);
printf("%d\n",max(ST[lim][Shit[rim-lim+1]],ST[rim-(1<<Shit[rim-lim+1])+1][Shit[rim-lim+1]]));
}
return 0;
}
Question 02[ACP2152 机器人]
仍然是模板题
同时可以用单调队列做
只不过必须使区间长度一定才能使用
Code
#include<bits/stdc++.h>
using namespace std;
const int N=100989;
deque<int> gmaxi,gmini;
int lable[N];
int main(){
gmaxi.clear();
gmini.clear();
int n,k;
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d",&lable[i]);
while(!gmaxi.empty()&&gmaxi.front()+k-1<i) gmaxi.pop_front();
while(!gmaxi.empty()&&lable[gmaxi.back()]<lable[i]) gmaxi.pop_back();
while(!gmini.empty()&&gmini.front()+k-1<i) gmini.pop_front();
while(!gmini.empty()&&lable[gmini.back()]>lable[i]) gmini.pop_back();
gmaxi.push_back(i);
gmini.push_back(i);
if(i>=k)printf("%d %d\n",lable[gmaxi.front()],lable[gmini.front()]);
}
return 0;
}
Question 03[ACP2154 记忆]
仍然是模板题
Code
#include<bits/stdc++.h>
using namespace std;
const int N=1000347,LIM=23;
int n,m,lable[N],ST[N][LIM],Shit[N];
int main(){
int lim,rim;
Shit[1]=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&lable[i]),ST[i][0]=lable[i];
for(int i=2;i<=n;i++)Shit[i]=Shit[i>>1]+1;
for(int i=1;i<=21;i++)for(int j=1;j+(1<<i)-1<=n;j++){
ST[j][i]=max(ST[j][i-1],ST[j+(1<<(i-1))][i-1]);
}
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&lim,&rim);
printf("%d\n",max(ST[lim][Shit[rim-lim+1]],ST[rim-(1<<Shit[rim-lim+1])+1][Shit[rim-lim+1]]));
}
return 0;
}
# Part 02 LCA
LCA 是一个树上问题:求最近公共祖先
较常用的有倍增和 DFS 序
而他们都与 ST 表和 RMQ 问题有密切关系
# 倍增
倍增和 ST 表一样都是二进制
而倍增记录的是每个节点的二进制级祖先
我们处理出一个 father 数组
使 father[i][j] 表示 i 的 2^j 级祖先
然后先将两个节点调至同一深度
接下来同时跳 father
注意这里
我们为了防止溢出和方便操作
只在两个节点跳后依然不同的时候才会使用
最终的结果即为他们的父节点
Question 01 [P3379 LCA]
模板
Code
#include<bits/stdc++.h>
using namespace std;
const int N=500098,LIM=25;
int father[N][LIM+2],n,m,depth[N];
vector<int> line[N];
void dfs(int Root){
for(int i=1;i<=LIM;i++)father[Root][i]=father[father[Root][i-1]][i-1];
for(auto i:line[Root]){
if(i!=father[Root][0]){
father[i][0]=Root;
depth[i]=depth[Root]+1;
dfs(i);
}
}
}
int LCA(int a,int b){
if(depth[a]>depth[b])swap(a,b);
for(int i=LIM;i>=0;i--)if(depth[a]<=depth[father[b][i]])b=father[b][i];
if(a==b)return a;
for(int i=LIM;i>=0;i--)if(father[a][i]!=father[b][i])a=father[a][i],b=father[b][i];
//If you are going to deo sth for each node, don't forget here!
return father[a][0];
}
int main(){
int a,b,ROOT;
scanf("%d%d%d",&n,&m,&ROOT);
for(int i=1;i<n;i++)scanf("%d%d",&a,&b),line[a].push_back(b),line[b].push_back(a);
depth[ROOT]=1,father[ROOT][0]=0;
dfs(ROOT);
for(int i=1;i<=m;i++){
scanf("%d%d",&a,&b);
printf("%d\n",LCA(a,b));
}
return 0;
}
Question 02 [ACP2162 点的距离]
算一下深度即可
Code
#include<bits/stdc++.h>
using namespace std;
const int N=100098,LIM=25;
int father[N][LIM+2],n,m,depth[N];
vector<int> line[N];
void dfs(int Root){
for(int i=1;i<=LIM;i++)father[Root][i]=father[father[Root][i-1]][i-1];
for(auto i:line[Root]){
if(i!=father[Root][0]){
father[i][0]=Root;
depth[i]=depth[Root]+1;
dfs(i);
}
}
}
int LCA(int a,int b){
if(depth[a]>depth[b])swap(a,b);
for(int i=LIM;i>=0;i--)if(depth[a]<=depth[father[b][i]])b=father[b][i];
if(a==b)return a;
for(int i=LIM;i>=0;i--)if(father[a][i]!=father[b][i])a=father[a][i],b=father[b][i];
//If you are going to deo sth for each node, don't forget here!
return father[a][0];
}
int main(){
int a,b;
scanf("%d",&n);
for(int i=1;i<n;i++)scanf("%d%d",&a,&b),line[a].push_back(b),line[b].push_back(a);
depth[1]=1,father[1][0]=0;
dfs(1);
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&a,&b);
printf("%d\n",depth[a]+depth[b]-depth[LCA(a,b)]*2);
}
return 0;
}
Quetion 03[ACP2167 祖孙询问]
判断 LCA 是否是两节点中一个即可
Code
#include<bits/stdc++.h>
using namespace std;
const int N=100098,LIM=25;
int father[N][LIM+2],n,m,depth[N];
vector<int> line[N];
void dfs(int Root){
for(int i=1;i<=LIM;i++)father[Root][i]=father[father[Root][i-1]][i-1];
for(auto i:line[Root]){
if(i!=father[Root][0]){
father[i][0]=Root;
depth[i]=depth[Root]+1;
dfs(i);
}
}
}
int LCA(int a,int b){
if(depth[a]>depth[b])swap(a,b);
for(int i=LIM;i>=0;i--)if(depth[a]<=depth[father[b][i]])b=father[b][i];
if(a==b)return a;
for(int i=LIM;i>=0;i--)if(father[a][i]!=father[b][i])a=father[a][i],b=father[b][i];
//If you are going to deo sth for each node, don't forget here!
return father[a][0];
}
int main(){
int a,b,ROOT;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&a,&b);
if(a==-1){ROOT=b;continue;}
if(b==-1){ROOT=a;continue;}
line[a].push_back(b),line[b].push_back(a);
}
depth[ROOT]=1,father[ROOT][0]=0;
dfs(ROOT);
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&a,&b);
if(LCA(a,b)==a){
puts("1");
}else if(LCA(a,b)==b){
puts("2");
}else{
puts("0");
}
}
return 0;
}
# DFS 序
这个算法复杂度更优
查询 O(1),预处理 O(n log n)
思路
设两个节点 DFS 序分别为 a,b 且 a<b
找出 a+1 到 b 中深度最小的节点(RMQ 同时维护下标)
其父即为 LCA
(玄学算法)
Question 01 [P3379 LCA]
Code
#include<bits/stdc++.h>
using namespace std;
const int N=500783,LIM=25;
int n,m,ROOT,ST[N][LIM+2],pos[N][LIM+2],depth[N],DFS_order[N],father[N],Shit[N],cnt,reverse_DFS[N];
vector<int> line[N];
void dfs(int pos){
//printf("dfs %d: id: %d dep: %d\n",cnt+1,pos,depth[pos]);
DFS_order[++cnt]=pos;
reverse_DFS[pos]=cnt;
for(auto i:line[pos]){
if(i!=father[pos]){
father[i]=pos;
depth[i]=depth[pos]+1;
dfs(i);
}
}
}
int LCA(int a,int b){
if(a==b)return a;
a=reverse_DFS[a],b=reverse_DFS[b];
if(a>b)swap(a,b);
a++;
if(ST[a][Shit[b-a+1]]>ST[b-(1<<Shit[b-a+1])+1][Shit[b-a+1]]){
//printf("min:%d\n",ST[b-(1<<Shit[b-a+1])+1][Shit[b-a+1]]);
return father[pos[b-(1<<Shit[b-a+1])+1][Shit[b-a+1]]];
}else{
// printf("min:%d\n",ST[a][Shit[b-a+1]]);
return father[pos[a][Shit[b-a+1]]];
}
}
int main(){
int tmp1,tmp2;
scanf("%d%d%d",&n,&m,&ROOT);
Shit[1]=0;
for(int i=2;i<=n;i++)Shit[i]=Shit[i>>1]+1;
for(int i=1;i<n;i++)scanf("%d%d",&tmp1,&tmp2),line[tmp1].push_back(tmp2),line[tmp2].push_back(tmp1);
depth[ROOT]=1,father[ROOT]=0;
dfs(ROOT);
for(int i=1;i<=n;i++)ST[i][0]=depth[DFS_order[i]],pos[i][0]=DFS_order[i];
for(int i=1;i<=LIM;i++)for(int j=1;j+(1<<i)-1<=n;j++){
if(ST[j][i-1]>ST[j+(1<<(i-1))][i-1]){
pos[j][i]=pos[j+(1<<(i-1))][i-1];
ST[j][i]=ST[j+(1<<(i-1))][i-1];
}else{
pos[j][i]=pos[j][i-1];
ST[j][i]=ST[j][i-1];
}
}
for(int i=1;i<=m;i++){
scanf("%d%d",&tmp1,&tmp2);
printf("%d\n",LCA(tmp1,tmp2));
}
return 0;
}
RMQ 和 LCA 问题的更多相关文章
- 【RMQ】洛谷P3379 RMQ求LCA
题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...
- RMQ求LCA
题目链接 rmq求LCA,interesting. 一直没有学这玩意儿是因为CTSC的Day1T2,当时我打的树剖LCA 65分,gxb打的rmq LCA 45分... 不过rmq理论复杂度还是小一点 ...
- BZOJ1906树上的蚂蚁&BZOJ3700发展城市——RMQ求LCA+树链的交
题目描述 众所周知,Hzwer学长是一名高富帅,他打算投入巨资发展一些小城市. Hzwer打算在城市中开N个宾馆,由于Hzwer非常壕,所以宾馆必须建在空中,但是这样就必须建立宾馆之间的连接通道.机智 ...
- poj 1330 Nearest Common Ancestors(LCA 基于二分搜索+st&rmq的LCA)
Nearest Common Ancestors Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 30147 Accept ...
- dfs序+RMQ求LCA详解
首先安利自己倍增求LCA的博客,前置(算不上)知识在此. LCA有3种求法:倍增求lca(上面qwq),树链剖分求lca(什么时候会了树链剖分再说.),还有,标题. 是的你也来和我一起学习这个了qwq ...
- RMQ和LCA
RMQ(Range Minimum/Maximum Query),即区间最值查询 查询很多的时候求[l,r]的最大值可以弄一个数组f[i,j]表示i~j的最大值 //这个是线段树 rmq是f[i,j] ...
- 数据结构 《18》----RMQ 与 LCA 的等价性 (一)
前言 RMQ: 数组 a0, a1, a2,..., an-1, 中求随意区间 a[i+1], a[i+2], ..., a[i+k] 的最小值 LCA: 求二叉树中两个节点的最低公共 ...
- LOJ#137. 最小瓶颈路 加强版(Kruskal重构树 rmq求LCA)
题意 三倍经验哇咔咔 #137. 最小瓶颈路 加强版 #6021. 「from CommonAnts」寻找 LCR #136. 最小瓶颈路 Sol 首先可以证明,两点之间边权最大值最小的路径一定是在最 ...
- hdu 2586 欧拉序+rmq 求lca
题意:求树上任意两点的距离 先说下欧拉序 对这颗树来说 欧拉序为 ABDBEGBACFHFCA 那欧拉序有啥用 这里先说第一个作用 求lca 对于一个欧拉序列,我们要求的两个点在欧拉序中的第一个位置之 ...
- st表、RMQ和LCA
int lca(int x,int y) { if(de[x]<de[y]) swap(x,y); int d=de[x]-de[y]; for(int i=log2(d);i>=0;i- ...
随机推荐
- Django_使用汇总(1)
使用django(4.1.5) 搭建股票信息后台,显示股票信息: Stock -> models.py class Stock(models.Model): symbol = models.Ch ...
- SpringBoot(四) - 整合Mybatis,逆向工程,JPA,Mybatis-plus
1.SpringBoot整合MyBatis 1.1 application.yml # 数据源配置 spring: datasource: driver-class-name: com.mysql.c ...
- rabbitmq部署及配置与验证-copy
1. 场景描述 朋友项目需要弄个测试环境,稍微帮忙了下,系统不复杂,但是需要自己安装mysql.Reids.Es.RabbitMq等,Mq主要用在同步用户信息与发送站内消息和短信上,RabbitMq以 ...
- 彻底讲透Spring AOP动态代理,原理源码深度剖析!
1.AOP:[动态代理]定义 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式: 2.基于注解aop的开发流程 1.导入aop模块:Spring AOP:(spring-asp ...
- biancheng-Python爬虫教程
http://c.biancheng.net/python_spider/ 网络爬虫又称网络蜘蛛.网络机器人,它是一种按照一定的规则自动浏览.检索网页信息的程序或者脚本.网络爬虫能够自动请求网页,并将 ...
- 深入浅出:Agent如何调用工具——从OpenAI Function Call到CrewAI框架
深入浅出:Agent如何调用工具--从OpenAI Function Call到CrewAI框架 嗨,大家好!作为一个喜欢折腾AI新技术的算法攻城狮,最近又学习了一些Agent工作流调用工具的文章,学 ...
- Linux计划任务定时备份数据
最近有项目需要定期备份mysql数据的需求,通过linux系统的crontab计划任务实现了一个简单demo,通过mysqldump命令对mysql数据进行备份. 首先新建一个脚本文件:mysqlba ...
- Hutch PG walkthrough Intermediate window
NMAP └─# nmap -p- -A -sS 192.168.196.122 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-16 01 ...
- uni-app无法触发onReachBottom事件
我们经常会遇见列表; 但是今天却发现下拉的时候: 无法触发onReachBottom事件; 原来是因为列表内容嵌套出现问题: 导致onReachBottom事件无法被触发 记住:列表内容如果是组件:外 ...
- 通过Ollama本地部署DeepSeek R1以及简单使用
本文介绍了在Windows环境下,通过Ollama来本地部署DeepSeek R1.该问包含了Ollama的下载.安装.安装目录迁移.大模型存储位置修改.下载DeepSeek以及通过Web UI来对话 ...