演算法總結--ST表

luokesi發表於2023-03-25

宣告(疊甲):鄙人水平有限,本文為作者的學習總結,僅供參考。


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表模板題題解
本文到此結束,希望對您有所幫助。

相關文章