# 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 问题的更多相关文章

  1. 【RMQ】洛谷P3379 RMQ求LCA

    题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...

  2. RMQ求LCA

    题目链接 rmq求LCA,interesting. 一直没有学这玩意儿是因为CTSC的Day1T2,当时我打的树剖LCA 65分,gxb打的rmq LCA 45分... 不过rmq理论复杂度还是小一点 ...

  3. BZOJ1906树上的蚂蚁&BZOJ3700发展城市——RMQ求LCA+树链的交

    题目描述 众所周知,Hzwer学长是一名高富帅,他打算投入巨资发展一些小城市. Hzwer打算在城市中开N个宾馆,由于Hzwer非常壕,所以宾馆必须建在空中,但是这样就必须建立宾馆之间的连接通道.机智 ...

  4. poj 1330 Nearest Common Ancestors(LCA 基于二分搜索+st&rmq的LCA)

    Nearest Common Ancestors Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 30147   Accept ...

  5. dfs序+RMQ求LCA详解

    首先安利自己倍增求LCA的博客,前置(算不上)知识在此. LCA有3种求法:倍增求lca(上面qwq),树链剖分求lca(什么时候会了树链剖分再说.),还有,标题. 是的你也来和我一起学习这个了qwq ...

  6. RMQ和LCA

    RMQ(Range Minimum/Maximum Query),即区间最值查询 查询很多的时候求[l,r]的最大值可以弄一个数组f[i,j]表示i~j的最大值 //这个是线段树 rmq是f[i,j] ...

  7. 数据结构 《18》----RMQ 与 LCA 的等价性 (一)

    前言     RMQ: 数组 a0, a1, a2,..., an-1, 中求随意区间 a[i+1], a[i+2], ..., a[i+k] 的最小值     LCA: 求二叉树中两个节点的最低公共 ...

  8. LOJ#137. 最小瓶颈路 加强版(Kruskal重构树 rmq求LCA)

    题意 三倍经验哇咔咔 #137. 最小瓶颈路 加强版 #6021. 「from CommonAnts」寻找 LCR #136. 最小瓶颈路 Sol 首先可以证明,两点之间边权最大值最小的路径一定是在最 ...

  9. hdu 2586 欧拉序+rmq 求lca

    题意:求树上任意两点的距离 先说下欧拉序 对这颗树来说 欧拉序为 ABDBEGBACFHFCA 那欧拉序有啥用 这里先说第一个作用 求lca 对于一个欧拉序列,我们要求的两个点在欧拉序中的第一个位置之 ...

  10. 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- ...

随机推荐

  1. 修改leds-gpio.c 让GPIO LED在kernel启动时就开始闪烁

    内容提要: 客户需要在开机时就闪烁LED,并要求越快越好 diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index ...

  2. k8s列出所有未配置探针的deployment

    在 Kubernetes 中,探针(Probes)用于检测容器的健康状态,主要包括以下三种: Liveness Probe(存活探针):检测容器是否正在运行. Readiness Probe(就绪探针 ...

  3. w3cschool-微信小程序开发文档-云开发

    微信小程序云开发 介绍 2020-07-24 16:19 更新 开发者可以使用云开发开发微信小程序.小游戏,无需搭建服务器,即可使用云端能力. 云开发为开发者提供完整的原生云端支持和微信服务支持,弱化 ...

  4. 如何使用C++ STL中的链表list

    1.声明链表 list<数据类型> 链表名称: 比如: list<int> listName;  //创建一个空链表listName list<int> listN ...

  5. SQL Server 内存占用高分析及解决办法(超详细)

    SQL Server 内存占用高分析及解决办法(超详细) 一.问题 1.1.SQL Server内存占用高 ,内存不释放 1.2.SQL Server 内存使用策略 SQL Server对服务器内存的 ...

  6. 一文读懂ROS开发,解锁RK3562J + Ubuntu工业平台应用

    在工业智能化浪潮中,智能机器人设备是成为工业自动化体系的璀璨之星,而其核心 --ROS系统,更是机器人领域的集大成者.今天,和大家分享一个ROS开发案例,基于RK3562J + Ubuntu工业平台. ...

  7. NPOI与excelcnv.exe

    在使用NPOI解析一些比较古老的仪器生成的excel文件时,经常会这个错误:The supplied spreadsheet seems to be Excel 5.0/7.0 (BIFF5) for ...

  8. 使用TUniSQLMonitor监视SQL语句执行的耗时

    //代码来自官方论坛type TUniSqlMonitorEx = class(TUniSqlMonitor) private FExecuteDuration: Cardinal; FFinalEv ...

  9. Linux 服务器防火墙开放端口命令(iptables、firewalld和ufw)

    本文主要介绍Linux中,Centos.Ubuntu和Debian开放防火墙端口的命令(iptables.firewalld和ufw)方法. 1.Centos中开放端口 1.systemctl sta ...

  10. 2021 OWASP TOP 10

    OWASP TOP 10 2021年版Top 10有哪些变化? 2021年版Top 10产生了三个新类别,原有四个类别的命名和范围也发生了变化,且进行了一些 整合. 2017年 TOP 10 top ...