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;
	} 
posted on   2025ing  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验
点击右上角即可分享
微信分享提示