前言

发现自己忘记了ST表然后搞了一发就来学RMQ了。
注:接下来的时间复杂度标记方式为\(\text{O}{(数据预处理)} \sim \text{O}{(单次询问)}\)

简介

  • RMQ是英文 Range Maximum/Mininmum Query 的缩写,表示区间最大(最小)值

算法实现

ST表

  • 基于倍增思想。不支持修改

  • 在我看来是个DP,f[i][j]用来记录从i开始跳\(1<<j\)步达到的查询

  • 时间复杂度\(\text{O}{(n \log n)} \sim \text{O}{(1)}\)

  • 空间复杂度\(\text{O}{(n \log n)}\)

int n,m,lg[25];
int f[N][25];
inline int query(int l,int r){
    int k=log2(r-l+1);
    return max(f[l][k],f[r-lg[k]+1][k]);
}//可以覆盖整个区间
int main(){
    n=read();m=read();lg[0]=1;
    for(int i=1;i<=21;i++)lg[i]=lg[i-1]<<1;
    for(int i=1;i<=n;i++)f[i][0]=read();
    for(int k=1;k<=21;k++)
        for(int i=1;i+lg[k]-1<=n;i++){
            f[i][k]=max(f[i][k-1],f[i+lg[k-1]][k-1]);
        }
    for(int i=1,l,r;i<=m;i++){
        l=read();r=read();
        printf("%d\n",query(l,r));
    }
    return 0;
}

除此之外,ST表还可以维护其他的可重复贡献问题。

例题

[SCOI2007]降雨量

线段树

  • 时间复杂度\(\text{O}{(n)} \sim \text{O}{( \log n)}\)
  • 空间复杂度\(\text{O}{(n)}\)

Four Russian

  • 不愧是战斗民族发明出这么恶臭的暴力的算法:)

  • 在ST表的基础上进行序列分块。将每一块建立一个ST表,解决问题时通过ST表上的区间查询解决。

  • \(block=\log n\)时,预处理复杂度达到最优,为\(\text{O}{(n \log \log n)}\)

但在算法竞赛中一般将块大小设为\(\sqrt {n}\),然后预处理出每一块内前缀和后缀的RMQ,再暴力预处理出任意连续的整块直接的RMQ,时间复杂度为\(\text{O}{(n)}\)

  • 对于左右端点不在同一块内的询问,我们可以直接\(\text{O}{1}\)得到左端点所在块的后缀RMQ,左端点和右端点之间的连续整块RMQ,和右端点所在块的前缀RMQ,答案即为三者最值
  • 对于左右端点在同一块内的询问,我们可以暴力求出两点间的RMQ,时间复杂度为\(\text{O}{(n)}\),但是单个询问左右端点在同一块内的期望为\(\text{O}{\frac{\sqrt{n}}{n}}\),所以这种方法的时间复杂度为期望\(\text{O}{(n)}\)

而在算法竞赛中,我们并不用非常担心出题人卡掉这种算法,因为我们可以通过在\(\sqrt {n}\)的基础上随机微调块大小,很大程度上避免算法在根据特定块大小构造的数据中出现最坏情况。并且如果出题人想要卡掉这种方法,则暴力有可能可以通过。
————noip(毒瘤)

例题

由乃救爷爷
我终于写出了四毛子,必须贴!!

code

#include<bits/stdc++.h>
using namespace std;
namespace GenHelper{
    unsigned z1,z2,z3,z4,b;
    unsigned rand_(){
        b=((z1<<6)^z1)>>13;
        z1=((z1&4294967294U)<<18)^b;
        b=((z2<<2)^z2)>>27;
        z2=((z2&4294967288U)<<2)^b;
        b=((z3<<13)^z3)>>21;
        z3=((z3&4294967280U)<<7)^b;
        b=((z4<<3)^z4)>>12;
        z4=((z4&4294967168U)<<13)^b;
        return (z1^z2^z3^z4);
    }
}
void srand(unsigned x){
    using namespace GenHelper;
    z1=x;
    z2=(~x)^0x233333333U;
    z3=x^0x1234598766U;
    z4=(~x)+51;
}
int read(){
    using namespace GenHelper;
    int a=rand_()&32767;
    int b=rand_()&32767;
    return a*32768+b;
}

typedef unsigned long long uLL;
const int N=20000005;
const int maxb=4473,sumb=maxb+1005;
inline int input(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    return x*f;
}
int n,m,s,a[N+maxb];
int block,lim;
int f[sumb][maxb],lg[27],highbit[N+maxb];
int L[sumb][maxb],R[sumb][maxb];
uLL ans=0;
int main(){
    n=input();m=input();s=input();
    srand(unsigned(s));lg[0]=1;
    for(int i=1;i<=26;i++)lg[i]=lg[i-1]<<1;
    block=sqrt(n);
    lim=(n-1)/block;
    for(int i=0;i<n;i++){
        a[i]=read();
        f[i/block][0]=max(f[i/block][0],a[i]);
    }
    for(int i=lim;i>=0;i--)
        for(int k=1;i+lg[k]-1<=lim;k++)
            f[i][k]=max(f[i][k-1],f[i+lg[k-1]][k-1]);
    for(int i=0;i<=lim;i++){
        int now=i*block;
        L[i][0]=a[now];
        for(int k=1;k<block;k++){
            L[i][k]=max(L[i][k-1],a[now+k]);
        }
        R[i][block-1]=a[now+block-1];
        for(int k=block-2;k>=0;k--)
            R[i][k]=max(R[i][k+1],a[now+k]);
    }
    for(int k=0;lg[k]<=n;k++)
        for(int i=lg[k];i<lg[k+1];i++){
            if(i>n)break;
            highbit[i]=k;
        }
    for(int i=1;i<=m;i++){
        int l=read()%n,r=read()%n;
        if(l>r)swap(l,r);
        int bl=l/block,br=r/block;
        int now=0;
        if(bl==br){
            for(int i=l;i<=r;i++)
                now=max(now,a[i]);
        }else {
            now=max(now,R[bl][l%block]);
            now=max(now,L[br][r%block]);
            int len=br-bl-1;
            int k=highbit[len];
            if(len){
                now=max(now,f[bl+1][k]);
                now=max(now,f[br-lg[k]][k]);
            }
        }
        ans+=unsigned(now);
    }
    printf("%llu",ans);
    return 0;
}

加减1RMQ

  • 若序列满足相邻两元素相差为 1,在这个序列上做 RMQ 可以成为加减 1RMQ

  • 由于相邻两个数字的差值为\(1\),所以在固定左端点数字时长度不超过$ \log n\(的右侧序列种类数为\)\sum_{i=1}^{i \le \log n}2^{i-1}\(,而这个式子显然不超过\)n\(。可以预处理出所以不超过\)n$种情况的最小值-第一个元素的值。

  • 我们可以用它来优化四毛子预处理

    • 预处理同一块内相邻两个数字直接的差,并且用二进制将其表示出来,在询问时找到询问区间对应的二进制表示查表得出答案

预处理时间可以优化到\(O(n)\)

笛卡尔树

  • 将普通RMQ转换为LCA问题,时间复杂度为\(\text{O}{(n)} \sim \text{O}{(\log n)}\),常数小但会被卡
  • 可以进而转化为加减1RMQ问题,时间复杂度为\(\text{O}{(n)} \sim \text{O}{1}\),常数大

code

int n,m,s,a[N];
int top,sta[N];
int ls[N],rs[N];
unsigned long long ans;
inline int query(int x,int l,int r){
    while(x<l||x>r)
        x=x<l?rs[x]:ls[x];
    return x;
}
int main(){
    n=input();m=input();s=input();srand(s);
    for(int i=1;i<=n;i++){
        a[i]=read();
        int k=top;
        while(k&&a[i]>a[sta[k]])k--;
        if(k)rs[sta[k]]=i;
        if(k<top)ls[i]=sta[k+1];
        sta[++k]=i;top=k;
    }
    int root=sta[1];
    for(int i=1,l,r;i<=m;i++){
        l=read()%n+1;r=read()%n+1;
        if(l>r)swap(l,r);
        ans+=a[query(root,l,r)];
    }
    printf("%llu",ans);
    return 0;
}

这是直接暴力查找的,大概就这个意思。是由乃救爷爷那个题的。因为暴力查找只能过随机化的数据啊悲

基于状压的线性RMQ做法

请巨佬们自己研究吧!!