ST表——楊子曰數據結構

ST表——楊子曰數據結構

今天我們來曰一種O(1)查詢的數據結構——ST表

它,就是RMQ問題的剋星!

給你一個數列,對於詢問[l,r],輸出區間[l,r]內的最大值(你喜歡最小值也可以啦!)

這……我用線段樹O(log n)(搞一搞不久好了嗎?

不好!!!我們是追求速度的人,由於它沒有更新操作,我們可以做到O(1)查詢

我們的ST表閃亮登場!!

(在下面的東西前你也可以先閱讀一下:談談最近公共祖先(LCA)——楊子曰算法,有助於理解)


黑餵狗:

ST表最最重要的思想就是——倍增

我們開一個數組 f[i][j]f[i][j]表示區間[i,i+2j1][i,i+2^j-1]內的最大值,也就是從a[i]a[i]開始數2j2^j個數裏面的最大值

Look at the 圖,比如這就是f[3][2]f[3][2]表示的最大值區間:
在這裏插入圖片描述

好的,理解了這個數組的含義以後我們來看,怎麼求出這個倍增數組?

掃一遍

初始值很簡單f[i][0]=a[i]f[i][0]=a[i]

那如何轉移呢?

其實就一句話(和倍增求LCA一樣):

f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);

很好理解吧!

我們來舉個例子,比如我們現在就是要求f[3][2]f[3][2],我們可以從f[3][1]f[3][1]f[5][1]f[5][1]轉移過來,也就是要把我們要求的區間一剖爲二:
在這裏插入圖片描述

完美,我們成功把預處理的複雜度做到了O(n log n):

for (int i=1;i<=n;i++){
	f[i][0]=a[i];
}
for (int j=1;j<=20;j++){//注意i和j的循環順序
	for (int i=1;i+(1<<j)-1<=n;i++){
		f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
	} 
}

好的,我們現在已經得到了f數組,那我們如何利用它去求出區間最大值呢?

區間的長度不一定是2的整數次冪呀!腫麼辦!!!

太簡單了,比如我們要查詢區間[2,7][2,7]的最大值,我把它變成max(f[2][2],f[4][2])max(f[2][2],f[4][2]):
在這裏插入圖片描述
兩段區間重疊在一起完全沒有關係的呀!

也就是說,如果查詢的區間是[l,r][l,r],我們給出的答案就是:max(f[l][l+log2(rl+1)],f[l2log2(rl+1)][log2(rl+1)])max(f[l][l+\lfloor log_2(r-l+1) \rfloor],f[l-2^{\lfloor log_2(r-l+1) \rfloor}][\lfloor log_2(r-l+1) \rfloor])

仔細地研究一下這個式子,它講的其實就是找到一個小於區間長度(r-l+1)的最大2的整數次冪,作爲我們重疊的兩段小區間的長度,然後取靠着左端點的這段,和靠着右端點的這段去一個max就行了!

OK,完事


c++代碼(洛谷【P3865】):

#include<bits/stdc++.h>
using namespace std;

const int maxn=100005;

int a[maxn],f[maxn][35],lg[maxn];

int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	lg[1]=0;
	for (int i=2;i<=n;i++) lg[i]=lg[i/2]+1;
	for (int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		f[i][0]=a[i];
	}
	for (int j=1;j<=20;j++){
		for (int i=1;i+(1<<j)-1<=n;i++){
			f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
		} 
	}
	while(m--){
		int l,r;
		scanf("%d%d",&l,&r);
		int tmp=lg[r-l+1];
		printf("%d\n",max(f[l][tmp],f[r-(1<<tmp)+1][tmp]));
	}
	return 0;
}

ST表:哈哈哈,打不過我吧,沒辦法我就是這麼強大!

線段樹:你動態更新一個個我看看

ST表:……

於HG機房

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章