淺談ST表
原題是P3865
ST表是一種資料結構
ST表類似於樹狀陣列和線段數的資料結構,能夠快速訪問一定區間內的最大值/最小值的值(解決RMQ問題:Range Minimum/Maximum Query,即區間最值查詢)問題的離線演算法。
其預處理的時間複雜度與線段樹,樹狀陣列相同,為O(n*logn)。
但ST表查詢的時間複雜度為O(1),而線段樹和樹狀陣列查詢的時間複雜度都為O(logn)。是不是更加優秀了呢!
首先我們來表示一下(以儲存最大值為例):
void do_it(){ for(int i=1;i<=n;i++) st[i][0]=a[i]; for(int i=1;(1<<i)<=n;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]); } } return ; }
首先我們需要說明一下,st[i][j]的意義表示從i開始向右數pow(2,j)位中的最大值
由於我們在內層迴圈中有判斷j+(1<<i)-1<=n條件,所以一定會有i+pow(2,j)-1<=n
所以我們很容易得到st[i][j]一定是合法的範圍內的
然後我們需要證明一個很簡單的定理:
當我們將log2x向下取整時,有2log2x >x/2 ①
來證明一下:
因為我們將log2x向下取整,假定x==pow(2,n),則log2x==n
所以我們有當pow(2,n)<= x < pow(2,n+1)時的log2x均為n
所以就有2log2x == pow(2,n),因為易得有pow(2,n)>pow(2,n+1)/2
因為x<pow(2,n+1)
所以有2log2x > x/2
QED.
然後我們現在還要考慮如何求得向下取整的log2x 這一問題
1.通過對數運演算法則logab==logxb/logxa,以及呼叫cmath庫中的log函式實現
x=log(b)/log(a)
(x是我們要求的logab)
優點:方便
缺點:當查詢的次數很多時,很有可能使得時間複雜度大大增加
2.預處理得到範圍內的所有logn
優點:訪問方便且訪問的時間複雜度為O(1)
缺點:訪問次數很少是預處理會增加很大的時間複雜度
但是我們知道預處理的時間複雜度其實也是O(1)的,只不過只是一個很大的常數罷了,只要在範圍內就不會TLE
那麼我們如何範圍內一定範圍內的最大值呢?
因為我們已經有定理①,所以當我們要求i到i+pow(2,j)-1範圍內的最大值時,我們只需要求i到i+pow(2,j-1)的最大值和(i+pow(2,j)-1)-pow(2,j-1)+1到i+pow(2,j)-1範圍內的最大值並且取兩者中德較大值就可以了!
AC程式碼:
#include<cctype> #include<iostream> #include<cstdio> using namespace std; const int N = 100010; int lg[N]; int n,m; int a[N]; int st[N][20]; int l,r; int read() { int x = 1,a = 0; char ch = getchar(); while(ch < '0' || ch > '9'){//如果讀進來的不是數字…… if(ch == '-')x = -1;//判斷負號 ch = getchar(); } while(ch <= '9'&&ch >= '0'){//如果讀進來的是數字…… a = a * 10 + ch - '0'; ch = getchar(); } return x*a; } void init(){ lg[1]=0; for(int i=2;i<=n;i++){ lg[i]=lg[i/2]+1; } return ; } void do_it(){ for(int i=1;i<=n;i++) st[i][0]=a[i]; for(int i=1;(1<<i)<=n;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]); } } return ; } int main(){ n=read(); m=read(); for(int i=1;i<=n;i++){ a[i]=read(); } init(); do_it(); for(int i=1;i<=m;i++){ l=read(); r=read(); printf("%d\n",max(st[l][lg[r-l+1]],st[r-(1<<lg[r-l+1])+1][lg[r-l+1]])); } return 0; }