RMQ


前言

發現自己忘記了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做法

請巨佬們自己研究吧!!

練習

板子題

【模板】ST 表

  • ST表和暴力查詢的笛卡爾樹都能水過

[SCOI2007]降雨量

  • 給定年份和降雨量問“X年是自Y年以來降雨量最多的”這句話必真、必假還是有可能
  • map映射,小細節多

下面是一些例題

例題

一、由乃救爺爺

  • 求RMQ以及詢問,\(n,m \le 20000000\)。看這個數據范圍用ST表直接GG。

  • 兩種做法:

    • \(\sqrt{n}\)分塊:預處理時間復雜度\(\text{O}{(n)}\),查找時在同一塊內的期望為\(\text{O}{(\frac{\sqrt{n}}{n})}\),所以暴力查找的時間復雜度為\(\text{O}{(n)}\),其他的查找為\(\text{O}{(1)}\)。所以時間復雜度為\(\text{O}{(n)}\)。空間復雜度\(\text{O}{(4n)}\)不會炸。

      這種方法並不好卡,因為卡該算法\(\sqrt{n}\)的暴力時有可能會讓真正的暴力通過

    • 笛卡爾樹:因為是隨機數據,所以可以直接暴力查找。預處理\(O(n)\),查找\(\text{O}{(\log n)}\)的,跑得飛快(指快過分塊)。空間復雜度\(\text{O}{(4n)}\)不會炸。

二、[HAOI2007]理想的正方形

  • 求長為a,寬為b的矩形邊長為n的min(RMaxQ-RMinQ)。\(2 \le n \le m,n \le a,n \le b,n \le 100\)

  • 單調隊列優化的二維RMQ(霧)

  • 暴力的想法很簡單,用ST表,每行求RMQ,然后枚舉。

  • 在暴力的想法之上,仍舊是枚舉每行每列,但是可以先枚舉每列然后枚舉行,利用單調隊列來維護行上在要取的\(n\)為邊長正方形內的Max和Min即可

->其實直接用單調隊列一枚到底(指在行上也直接用單調隊列維護預處理)也行。

三、[NOI2010] 超級鋼琴

  • 老經典題了大多數人都做過,但題太少了所以……(霧)

  • 貪心+堆維護最優

  • 用RMQ記錄下標,找[l,r]最大的子段和的下標

  • 然后用大根堆每次取堆頂然后再扔進去[l,pos-1],[pos+1,r](pos是最優的位置)取k次即可

    四、[HNOI2016]序列

  • 求解\(\sum_{i=l}^{r}\sum_{j=i+1}^{r} \space \min{a[i \sim j]}\)

  • 發現題解里好多用莫隊的,還有因為隨機數據笛卡爾樹和線段樹莽的,還有各種神奇數據結構的

img
  • 實際上這個題的莫隊就是普通莫隊只是用來簡單的優化查詢而已,其他的用數據結構的是在線做法,比如什么線段樹掃描線啊貓樹啊奇怪的樹狀數組啊,對比之下莫隊不就香香了嘛。(反正大家都會普通莫隊……的吧)
  • 莫隊實現主要在於如何維護[l,r+1] (知道這個了自然也就知道[l-1,r],[l,r-1],[l+1,r]了)。發現本次增加的是[l,r+1],[l+1,r+1]...[r+1,r+1],考慮[l,r+1]之間最小值的位置為p。則[l,p]的最小值一點為a[p],貢獻為\(a[p]\times (p-l+1)\),可以用RMQ來解決。
  • [p+1,r+1]內的數一定全部比a[p]大,所以一定有向左第一個比當前小的a[x]的位置為p。 設f[l] [r]表示以r為右端點,左端點在[l,r]的區間的答案。記L[i]表示從i向前第一個比i小的數的位置,則左端點在[L[r],r]的區間最小值都是a[r],所以\(f[l][r]=f[l][L[r]]+a[r]\times(r-L[r])\),發現與l無關可以去掉這一維,即\(f[r]=f[L[r]]+a[r]\times(r-L[r])\)。 又知道\(L[x],x\in[p+1,r+1]\)至少有一個p,所以一定可以計算出f[p+1] [r+1]
  • \(f_{r+1}=a_{r+1}\times (r+1-pre_{r+1})+...+a_x\times (x-p)+f_p\)。所以\(f_{r+1}-f_p\)就是要求的f[p+1] [r+1]

五、水の數列

事實證明毒瘤題都是多次強化出來的(霧

  • 給定一個長度為\(N\)的數列,選擇\(x\),將小於等於\(x\)的數標記,且得到的區間個數在\(l \sim r\)范圍內,得到的得分是每一個連續標記區間的長度的平方和/x,T組詢問,強制在線
  • \(1 \le N \le 10^6,1 \le T \le 10^3,1 \le num_i \le 10^6\)
  • 這……只能通過x來預處理所有情況了……吧?記錄每個x出現的位置,用len維護擴展的兩端的長度再記錄val用來更新當前x的ans即可。
  • 查詢的時候通過RMQ查詢分塊[l,r]內的最大即可,但因為塊的個數不可能超過\(n>>1\)個所以ST表可以開下。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM