# 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];
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];
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];
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){
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]]){
return father[pos[b-(1<<Shit[b-a+1])+1][Shit[b-a+1]]];
}else{
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;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验