【算法】ST表


想學習一下LCA倍增,先 水一個黃題 學一下ST表

ST表

介紹:

這是一個運用倍增思想,通過動態規划來計算區間最值的算法

算法步驟:

  1. 求出區間最值

  2. 回答詢問

求出區間最值:

\(f[i][j]\) 來存儲從第 \(j\) 個點開始,向后 \(2 ^ i - 1\) 個點(共 \(2 ^ i\) 個點)中的最值(包括本身)

利用二分法的思想,將區間 \([ j, ~j + 2 ^ i- 1 ]\) 平均(大概)分成兩半

可以算出,區間 \([ j, ~j + 2 ^ i - 1 ]\) 的長度為 \(2 ^ i\)

所以一半的長度為 \(2 ^ {i - 1}\)

那么分成的兩個區間就為 \([j, ~j + 2 ^ {i - 1} - 1 ]\)\([ j + 2 ^ {i - 1}, ~j + 2 ^ i - 1 ]\)

毫無疑問,

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

這樣遞推式就出來了

現在來想一下:
\(f[0][j]\) 就是從 \(j\) 開始向后數第 \(2 ^ 0 - 1\) 個點的最值,區間為 \([j,~j]\)(區間不能這么寫,這里請不要較真)

不用考慮,\(f[0][j]\) 就是第 \(j\) 個數本身

好了,現在邊界也得出來了,可以開始 dp 了

上代碼:

void prew() {
	for (int i = 1; i <= n; ++i) f[0][i] = a[i]; // 給邊界賦值,a[i] 存的是數列的第 i 個數
	int kf = log2(n); // 得出數列最多可以向后跳幾個 2 的冪,n 為數列長度
	for (int i = 1; i <= kf; ++i) { // 枚舉區間的長度 2 ^ i
	      for (int j = 1; j + (1 << i) - 1 <= n; j++) // 枚舉起點
		      f[i][j] = max(f[i - 1][j], f[i - 1][j + (1 << i - 1)]); // 遞推式
	}
}

一個小問題:

由於 \(log_2\) 運算可能會出現實數,而我們又用整數類型來存儲,所以可能會出現兩個區間不能完全覆蓋整個區間的情況,得出的 \(f[i][j]\) 不夠精准

最簡單的方法就是用兩個區間覆蓋

反正又沒要求兩個區間不能重疊~~

可以選用 \(f[k][l]\)\(f[k][r-(1<<k)+1]\) 來覆蓋 \(f[l][r]\)

所以 $$f[l][r] = max(f[k][l],f[k][r -(1 << k)+ 1])$$(\(k\) 為區間 \([l,~r]\) 的長度的 \(log_2\)

再上代碼:

int ask(int l, int r) {
	int k = log2(r - l + 1); // 求出區間最大的 log2 值
	return max(f[k][l], f[k][r - (1 << k) + 1]); // 返回區間 [l,r] 的最大值
}

完整代碼:

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define MAXN 100010
#define int long long
using namespace std;
//-------------定義結構體-----------

//--------------定義變量------------
int f[31][MAXN], Log2[MAXN], a[MAXN]; // f[i][j]表示從 j 往后 2 ^ i - 1 個數中的最大值 
int n, m;
//--------------定義函數------------
inline int read() {
	int x = 0, f = 0; char ch = getchar();
	while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
	while (isdigit(ch)) x = x * 10 + (ch ^ 48), ch = getchar();
	return f ? -x : x;
}
int max(int a, int b) {
	return a > b ? a : b;
}
int min(int a, int b) {
	return a < b ? a : b;
}
void swap(int &a, int &b) {
	a ^= b ^= a ^= b;
}
void pre() { // 預處理 dp
	Log2[0] = -1;
	for(int i = 1; i <= 100000; ++i) Log2[i] = Log2[i >> 1] + 1;
	for (int i = 1; i <= n; ++i) f[0][i] = a[i];
	for (int i = 1; i <= Log2[n]; ++i) {
		for (int j = 1; j + (1 << i) - 1 <= n; j++)
			f[i][j] = max(f[i - 1][j], f[i - 1][j + (1 << i - 1)]);
	}
}
int ask(int l, int r) { // 回答詢問
	int k = Log2[r - l + 1];
	return max(f[k][l], f[k][r - (1 << k) + 1]);
}
//---------------主函數-------------
signed main() {
	int l, r, ans;
    n = read(); m = read();
    for (int i = 1; i <= n; ++i) a[i] = read();
    pre();
    for (int i = 1; i <= m; ++i) {
    	l = read(); r = read();
    	ans = ask(l, r);
    	printf("%d\n", ans);
    }
	return 0;
}

模板題:
洛谷P3865


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM