区间最值
区间最值的求法通常被称为RMQ问题。
解决这类问题的方法有很多,本文主要介绍几种简单易懂且容易实现的方法。
- 本文分别以 P1816 忠诚 和 P3865【模板】ST表 为例解释区间最大值/最小值的求法。
一 朴素算法
也就是常说的枚举,枚举每个区间找出最小值/最大值,时间复杂度为
二 单调队列
单调队列主要用来解决一类名为 滑动窗口 的问题。
单调队列主要流程如下,当窗口中元素不满的时候直接把元素加入到窗口中,如果新加入到元素比对尾的元素更小/更大,那么对尾的元素必然不可能对答案产生贡献了,直接将该元素从窗口中删去即可。
int min_deque(){
int h = 1 , t = 0;
for(int i=1;i<=n;i++){
while(h <= t && q1[h] + m <= i) h++;
while(h <= t && a[i] < a[q1[t]]) t--;
q1[++t] = i;
if(i >= m) printf("%lld ",a[q1[h]]);
}
printf("\n");
}
int max_deque(){
int h = 1 , t = 0;
for(int i=1;i<=n;i++){
while(h <= t && q2[h] + m <= i) h++;
while(h <= t && a[i] > a[q2[t]]) t--;
q2[++t] = i;
if(i >= m) printf("%lld ",a[q2[h]]);
}
}
三 ST表
ST表基于倍增和动态规划思想,首先定义数组
我们可以把刚才提到的那段长度为
显然
接下来就是查询操作了,考虑找出一个刚好覆盖了整个区间一半的长度,即
所以答案就是
int main(){
int n = read() , m = read();
for(int i=1;i<=n;i++){
a[i] = read();
}
lg[0] = -1;
for(int i=1;i<=n;i++){
f[i][0] = a[i];
lg[i] = lg[i>>1] + 1;
}
for(int j=1;j<=lg[n];j++){
for(int i=1;i<=n-(1<<j)+1;i++){
f[i][j] = max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
for(int i=1;i<=m;i++){
int l = read() , r = read();
int k = lg[r - l + 1];
ans = max(f[l][k] , f[r - (1<<k) + 1][k]);
printf("%lld\n",ans);
}
return 0;
}
四 线段树
线段树是维护此类区间问题的最常用的方法之一
处理此类无修改操作的区间问题,线段树只需要建树和查询这两个基本操作就能快速的维护区间最值。
struct node{
int l , r , ans = INF;
}tree[N<<2];
void push_up(int p){
tree[p].ans = min(tree[p<<1].ans , tree[p<<1|1].ans);
}
void build(int p,int l,int r){
tree[p].l = l , tree[p].r = r;
if(l == r){
tree[p].ans = a[l];
return;
}
int mid = l + r >> 1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
push_up(p);
}
inline int query(int p,int l,int r){
if(l <= tree[p].l && tree[p].r <= r){
return tree[p].ans;
}
int mid = tree[p].l + tree[p].r >> 1;
if(r <= mid) return query(p<<1,l,r);
if(l > mid) return query(p<<1|1,l,r);
return min(query(p<<1,l,mid),query(p<<1|1,mid+1,r));
}
int main(){
int n = read() , m = read();
for(int i=1;i<=n;i++){
a[i] = read();
}
build(1,1,n);
for(int i=1;i<=m;i++){
int L = read() , R = read();
printf("%lld ",query(1,L,R));
}
return 0;
}
五 树状数组
树状数组的主要功能是维护区间和,在处理区间最值的时候,我们只需要用原先树状数组中用来维护区间和的数组
本文不再赘述树状数组的基本操作,只解释一下求最值的方法
我们考虑仿照求区间和的操作将区间
-
当
时:显然可以将 分成 和 这两个区间,这样拆分有什么好处呢?仔细观察不难发现后者其实就是 这样,我们就将求最值的区间直接减少了一半。 -
当
时:此时就不能像刚才一样拆分了,这时考虑将区间 拆分成 和 看上去效率不高,但是经过这样拆分之后,区间 有可能满足第一种情况,所以效率其实还在我们可以接受的范围之内。
上述过程可以选择递归完成也可以循环实现
void update(int x,int k){
a[x] = k;
while(x <= n){
c[x] = min(c[x] , k);
x += lowbit(x);
}
}
int find_min(int l,int r){
int ans = a[r];
while(l != r){
r--;
while(r - lowbit(r) >= l){
ans = min(ans,c[r]);
r -= lowbit(r);
}
ans = min(ans,a[r]);
}
return ans;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】