$RMQ$問題:給定一個長度為$N$的區間,$M$個詢問,每次詢問$[L_i,R_i]$這段區間元素的最大值/最小值。
$RMQ$的高級寫法一般有兩種,即為線段樹和$ST$表。
本文主要講解一下$ST$表的寫法。(以區間最大值為例)
$ST$表:一種利用$dp$思想求解區間最值的倍增算法。
定義:$f(i,j)$表示$[i,i+2^{j}-1]$這段長度為$2^{j}$的區間中的最大值。
預處理:$f(i,0)=a_i$。即$[i,i]$區間的最大值就是$a_i$。
狀態轉移:將$[i,j]$平均分成兩段,一段為$[i,i+2^{j-1}-1]$,另一段為$[i+2^{j-1},i+2^{j}-1]$。
兩段的長度均為$2^{j-1}$。$f(i,j)$的最大值即這兩段的最大值中的最大值。
得到$f(i,j)=max\{f(i,j-1),f(i+2^{j-1},j-1)\}$。
void RMQ(int N){ /*注意外部循環從j開始, 因為初始狀態為f[i][0], 以i為外層會有一些狀態遍歷不到。*/ for(int j=1;j<=20;j++) for(int i=1;i<=N;i++) if(i+(1<<j)-1<=N) f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]); }
查詢:若需要查詢的區間為$[i,j]$,則需要找到兩個覆蓋這個閉區間的最小冪區間。
這兩個區間可以重復,因為兩個區間是否相交對區間最值沒有影響。(如下圖)
當然求區間和就肯定不行了。這也就是$RMQ$的限制性。
因為區間的長度為$j-i+1$,所以可以取$k=log_2(j-i+1)$。
則$RMQ(A,i,j)=max\{f(i,k),f(j-2^{k}+1,k)\}$。
代碼:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; int a[100001],f[100001][20]; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } void RMQ(int N){ for(int j=1;j<=20;j++) for(int i=1;i<=N;i++) if(i+(1<<j)-1<=N) f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]); } int main(){ int N=read(),M=read(); for(int i=1;i<=N;i++) a[i]=read(); for(int i=1;i<=N;i++) f[i][0]=a[i]; RMQ(N); while(M--){ int i=read(),j=read(); int k=(int)(log((double)(j-i+1))/log(2.0)); printf("%d\n",max(f[i][k],f[j-(1<<k)+1][k])); } return 0; }