宣告(
疊甲):鄙人水平有限,本文為作者的學習總結,僅供參考。
1. RMQ 介紹
在開始介紹 ST 表前,我們先了解以下它以用的場景 RMQ問題 。RMQ (Range Minimum/Maximum Query)問題是指:對於長度為n的數列A,回答若干詢問RMQ(A,i,j)(i,j<=n),返回數列A中下標在i,j裡的最小(大)值,也就是說,RMQ問題是指求區間最值的問題,其主要的特徵是查詢的區間是靜態的。
在上一篇關於線段樹的文章中我們解決了動態的區間的維護,先是進行O(nlog(n))時間負載度的建樹預處理,然後就能以O(log(n))的時間複雜度進行維護與查詢。對於 RMQ 問題來說線段樹也是能過比較好的處理,總的時間複雜度為O(nlog(n)+log(n)),比暴力法的時間複雜O(n^2)還行快一些。
2. ST 表介紹
雖然線段樹也能比較好的解決 RMQ 問題,但是它的特性還是更加符合動態的情況,故對於靜態的來說就引入了 ST 表來進行解決。ST 表是先對資料進行 O(nlog(n)) 的預處理,然後就可以進行 O(1) 的查詢。(是常數!!!)
故,一般的 ST 表題的解法解法可以分為以下步驟:
【1】 進行預處理,一般來說使用動態規劃的思想進行的
【2】 進行查詢
3. 舉些栗子
3.1 ST 表模板題
題目描述
給定一個長度為 N 的數列,和 M 次詢問,求出每一次詢問的區間內數字的最大值。
這是一道 ST 表經典題——靜態區間最大值,根據上述的描述,解題思路如下(具體的數學關係就不做解釋,自己畫畫圖就可以推理出來的):
【1】 進行預處理 :這裡我們使用一個陣列 st[i][j] 進行打表,其含義為從第 i 個數開始數 2^j 個數中的最大值,故我們可以得到動態規劃的狀態轉移方程:
st[i][j] = max(st[i][j-1],st[i-(1<<j)][j-1])
【2】 進行查詢 : 對於區間 [l,r] 來說,我可以使用以下來表達其的最大值:
m = log2(r-l+1)
max[l,r] = max(st[l][m],st[r-(1<<m)][m])
根據以上的思路可以得到以下程式碼
#include <bits/stdc++.h>
using namespace std;
#define NMAX 100000
int n,m,x,y;
int st[NMAX+1][20]; // st[i][j] 表示從 i 開始 2^j 個數中需要的答案
// ST 表的查詢函式
int calc(int l,int r)
{
int m = log2(r - l + 1);
return max(st[l][m],st[r-(1<<m)+1][m]);
}
int main()
{
// [1] 獲取資料並進行預處理
cin >> n >> m;
for(int i = 1;i <= n;++i)
{
cin >> st[i][0];
}
// 需要注意的是我們要從 i 開始遍歷 st[i][j]
for(int j = 1; (1 << j) <= n;++j)
{
for(int i = 1;i + (1<<j) - 1 <= n;++i)
{
st[i][j] = max(st[i][j-1],st[i + (1<<(j-1))][j-1]);
}
}
// [2] 查詢
while(m--)
{
scanf("%d%d",&x,&y);
printf("%d\n",calc(x,y));
}
return 0;
}
3.2 質量檢測
題目描述
為了檢測生產流水線上總共 N 件產品的質量,我們首先給每一件產品打一個分數 A 表示其品質,然後統計前 M 件產品中質量最差的產品的分值 Q[m] = min{A_1, A_2, ... A_m},以及第 2 至第 \(M + 1\) 件的 Q[m + 1], Q[m + 2] ... 最後統計第 N - M + 1 至第 N 件的 Q[n]。根據 Q 再做進一步評估。
請你儘快求出 Q 序列。
解題思路如下
總的思路如上題一致,無非就是從查詢最大最變成了最少小值,以及查詢時給定了區間左右邊界的規定關係 [i,i+M-1]
具體 AC 程式碼如下
#include <bits/stdc++.h>
#define NMAX 1000000
using namespace std;
int m,n;
int st[NMAX+1][32];
int calc(int x,int y)
{
int m = log2(y-x+1);
return min(st[x][m],st[y-(1<<m)+1][m]);
}
int main()
{
cin >> n >> m;
// [1] 獲取資料,並進行預處理
for(int i = 1;i <= n;i++)
{
cin >> st[i][0];
}
for(int j = 1;(1<<j) - 2<= n;j++)
{
for(int i = 1;i+(1<<j)-1 <= n;i++)
{
st[i][j] = min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
}
// [2] 查詢
for(int i = m;i <= n;i++)
{
cout << calc(i-m+1,i) << endl;
}
return 0;
}
3.3 [藍橋杯 2022 省 A] 選數異或
題目描述
給定一個長度為 n 的數列 A1 A2 ... An 和一個非負整數 x, 給定 m 次查詢, 每次詢問能否從某個區間 [l, r] 中選擇兩個數使得他們的異或等於 x
這題一眼看出就是很明顯的靜態區間查詢問題,但是與上述中不同的是,這次查詢的不再是最值,而是滿足關係數對的下標。
總的解題思路還是不變的:
【1】 預處理:這裡的兩數異或我們可以聯想到兩數和的問題,故可以利用一個 Hash 陣列記錄其每個數的下標來輔助我們處理(具體實現見程式碼),需要注意的是我 ST 表中記錄的應該是與這個數滿足關係物件中的最近一個,故狀態轉移方程為:st[i] = max(st[i-1],Hash[Ai])
【2】 根據預處理得到的 ST 表,我們只要查表看該區間得到的值是否大於區間的左邊界值
AC 程式碼如下:
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n,m,x;
cin >> n >> m >> x;
map<int,int> Hash;
// st[i] 表示 1~i 中滿足關係的數對的最後出現的一個的下標
int st[n+1] = {0,};
// [1] 預處理
for(int i = 1;i <= n;i++)
{
int data;
cin >> data;
st[i] = max(st[i-1],Hash[data]);
Hash[data^x] = i;
}
// [2] 查詢
while(m--)
{
int l,r;
cin >> l >> r;
if(st[r] >= l) cout << "yes" << endl;
else cout << "no" << endl;
}
return 0;
}
4.參考
洛谷ST表模板題題解
本文到此結束,希望對您有所幫助。