想學習一下LCA倍增,先 水一個黃題 學一下ST表
ST表
介紹:
這是一個運用倍增思想,通過動態規划來計算區間最值的算法
算法步驟:
求出區間最值
回答詢問
求出區間最值:
用 \(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[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