ST表
1.什么是 RMQ & ST?
RMQ 是一种区间最值查询的问题,我们珂以用线段树或树状数组来解决,但今天我们要用的是——ST表。
要注意的是,ST表只珂以用于静态区间求最值,而动态区间就只能用线段树或树状数组了。
2.ST 算法思想简述
ST 算法使用了 DP 的思想。
我们用 \(f(i,j)\) 表示以 \(i\) 为起点,连续 \(2^j\) 个数中的最大值。例如 \(f(3,2)\) 就表示以 \(3\) 为起点,连续 \(2^2\) 个数中的最大值,即第 \(3\) 个数到第 \(6\) 个数的最大值。
1.预处理
记原数组为 \(a\),因为 \(2^0=1\),所以 \(f(i,0)=a_i\)。
对于 \(j\ne0\) 的情况,我们将其分为长度相等的两部分:\(f(i,j-1)\) 和 \(f(i+2^{j-1},j-1)\),那么它的最大值就是这两部分的最大值中较大的那一个。
于是有状态转移方程:
dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1])
预处理代码
void init()
{
for (int i = 1; i <= n; i++)
{
dp[i][0] = a[i];
}
for (int j = 1; (1 << j) <= n; j++)
{
for (int i = 1; i + (1 << j) - 1 <= n; i++)
{
dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
}
}
2.查询
我们记 \(L\) 表示区间 \([l,r]\) 的长度,则 \(L=r-l+1\),又设 \(k=\log(L)\)。
如果 \(L\) 不为 \(2\) 的次方,那么 \(2^k\) 就会 \(<L\),从而会有遗漏,因此答案不为 \(f(l,k)\)。但是 \(L<2^{k+1}\),所以只需要从 \(l\) 往右数 \(2^k\) 个,从 \(r\) 往左数 \(2^k\) 个即可,显然重复部分不影响结果。
于是有:
ans = max(dp[l][k], dp[r - (1 << k) + 1][k])
查询代码
int query(int l, int r)
{
int k = log2(r - l + 1);
return max(dp[l][k], dp[r - (1 << k) + 1][k]);
}
3.模板题
AC Code
#include <iostream>
#include <cstdio>
#include <cmath>
#define int long long
using namespace std;
const int MAXN = 1e5 + 5;
int n, m;
int a[MAXN], dp[MAXN][20];
void init()
{
for (int i = 1; i <= n; i++)
{
dp[i][0] = a[i];
}
for (int j = 1; (1 << j) <= n; j++)
{
for (int i = 1; i + (1 << j) - 1 <= n; i++)
{
dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
}
}
int query(int l, int r)
{
int k = log2(r - l + 1);
return max(dp[l][k], dp[r - (1 << k) + 1][k]);
}
signed main()
{
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &a[i]);
}
init();
while (m--)
{
int x, y;
scanf("%lld%lld", &x, &y);
printf("%lld\n", query(x, y));
}
return 0;
}