# 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. 在Ubuntu WSL2里配置GDAL Docker环境

    在Ubuntu WSL2里配置GDAL Docker环境 启用systemd # Ubuntu中执行 echo -e "[boot]\nsystemd=true" | sudo t ...

  2. c# 通过注册表判断有没有安装某个软件

    private bool checkHasInstalledSoftWare(string displayName) { Microsoft.Win32.RegistryKey uninstallNo ...

  3. biancheng-MongoDB教程

    目录http://c.biancheng.net/mongodb2/ 1NoSQL是什么2MongoDB是什么3Windows安装MongoDB4Linux安装MongoDB5MacOS安装Mongo ...

  4. 通过Nginx反向代理配置/.well-known/pki-validation/fileauth.txt步骤实例

    最近在某云平台上申请了SSL证书(https),SSL证书申请或者续期过程中需要进行域名验证. 如果域名验证类型选择[文件]方式,等你提交申请后,要在目标域名对应的服务端上传一个文件(通常是一个.tx ...

  5. 2025 最佳免费商用文本转语音模型: Kokoro TTS

    在文本转语音(TTS)技术领域,一项突破性的进展引起了广泛关注--Kokoro TTS 模型凭借其卓越性能和完全免费的商用许可,成为目前最出色的 TTS 解决方案之一.基于广受欢迎的开源框架 Styl ...

  6. VuePress 博客搭建系列 33 篇正式完结!

    前言 VuePress 博客搭建系列是我写的第 6 个系列文章,前 5 个系列分别是 JavaScript 深入系列,JavaScript 专题系列.underscore 系列.ES6 系列.Type ...

  7. uni-app配置顶部标题样式

    在pages.json中,通过配置这个文件,可以去设置当前页面的标题样式, 赋值的时候,将注册删除哈!!! 这样配置兼容 小程序和H5端 在配置的时候,没有给背景色,我还以为在uniapp中不兼容小程 ...

  8. Schreier–Sims 算法

    好看的实现. #include<bits/stdc++.h> using namespace std; #define int long long const int maxn=105; ...

  9. Luogu P10869 LCMs 题解 [ 黄 ] [ lcm ] [ 最短路 ]

    LCMs:很好的数论和构造题. 显然我们不可以直接建图跑最短路. 于是考虑分讨. 倍数关系 答案显然为 \(\max(a,b)\). 相等关系 答案显然为 \(0\). \(\gcd(a,b)> ...

  10. css快速入门系列 —— 移动开发闲谈

    移动开发闲谈(Flex和css 库) 背景 目前在做移动小程序开发,效果必须和设计稿一模一样,一个像素都不能有差异. 虽然公司也提供了图生文的工具,但是有时生成的代码可读性不太好,二次修改也比较费劲, ...