数据结构 - ST表 - 处理RMQ问题
1、ST表
是 一 种 用 于 解 决 R M Q ( R a n g e M i n i m u m / M a x i m u m Q u e r y , 即 区 间 最 值 查 询 ) 问 题 的 数 据 结 构 。 是一种用于解决RMQ(Range \ Minimum/Maximum \ Query,即区间最值查询)问题的数据结构。 是一种用于解决RMQ(Range Minimum/Maximum Query,即区间最值查询)问题的数据结构。
核 心 思 想 : 倍 增 。 将 一 段 区 间 平 均 一 分 为 二 , 以 两 个 子 区 间 的 最 值 更 新 当 前 区 间 的 最 值 。 核心思想:倍增。将一段区间平均一分为二,以两个子区间的最值更新当前区间的最值。 核心思想:倍增。将一段区间平均一分为二,以两个子区间的最值更新当前区间的最值。
定 义 S T 表 , f [ i ] [ j ] : 区 间 [ i , i + 2 j − 1 ] 内 的 最 值 。 定义ST表,f[i][j]:区间[i,i+2^j-1]内的最值。 定义ST表,f[i][j]:区间[i,i+2j−1]内的最值。
更 新 区 间 [ i , j ] , 可 对 子 区 间 [ i , i + 2 j − 1 − 1 ] 和 区 间 [ i + 2 i − 1 , i + 2 j − 1 ] 取 最 值 。 更新区间[i,j],可对子区间[i,i+2^{j-1}-1]和区间[i+2^{i-1},i+2^j-1]取最值。 更新区间[i,j],可对子区间[i,i+2j−1−1]和区间[i+2i−1,i+2j−1]取最值。
则 有 f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i + 2 j − 1 ] [ j − 1 ] ) 。 则有f[i][j]=max(f[i][j-1],f[i+2^{j-1}][j-1])。 则有f[i][j]=max(f[i][j−1],f[i+2j−1][j−1])。
由 于 l o g 2 x > x 2 , 故 查 询 区 间 [ l , r ] 的 最 值 时 , 记 区 间 长 度 t = r − l + 1 。 由于log_2x>\frac{x}{2},故查询区间[l,r]的最值时,记区间长度 t=r-l+1。 由于log2x>2x,故查询区间[l,r]的最值时,记区间长度t=r−l+1。
可 返 回 m a x ( f [ l ] [ l o g 2 t ] , f [ r − t + 1 ] [ l o g 2 t ] ) 。 可返回max(f[l][log_2t],f[r-t+1][log_2t])。 可返回max(f[l][log2t],f[r−t+1][log2t])。
参考——《ST表》。
模板:
int f[N][M]; //f[i][j]:区间[i,i+2^j-1]内的最值
void Init() //预处理2^k和logk
{
for(int i=0;i<M;i++) pow_2[i]=1<<i;
log_2[0]=-1;
for(int i=1;i<=n;i++) log_2[i]=log_2[i/2]+1;
}
void get_ST()
{
for(int i=1;i<=log_2[n];i++) //先枚举区间长度
for(int j=1;j+pow_2[i]-1<=n;j++)
f[j][i]=max(f[j][i-1],f[j+pow_2[i-1]][i-1]);
}
int query(int l,int r)
{
int k=log_2[r-l+1];
return max(f[l][k],f[r-pow_2[k]+1][k]);
}
例题2-1:洛谷 P3865 - ST表
题目背景
这是一道ST表经典题——静态区间最大值
请注意最大数据时限只有0.8s,数据强度不低,请务必保证你的每次查询复杂度为 O(1)
题目描述
给定一个长度为 N 的数列,和 M 次询问,求出每一次询问的区间内数字的最大值。
输入格式:
第一行包含两个整数 N, M,分别表示数列的长度和询问的个数。
第二行包含 N 个整数(记为 a i a_i ai),依次表示数列的第 i 项。
接下来 M 行,每行包含两个整数 l i , r i l_i, r_i li,ri,表示查询的区间为 [ l i , r i ] [ l_i, r_i] [li,ri]
输出格式:
输出包含 M 行,每行一个整数,依次表示每一次询问的结果。
输入样例:
8 8
9 3 1 7 5 6 0 8
1 6
1 5
2 7
2 6
1 8
4 8
3 7
1 8
输出样例:
9
9
7
7
9
8
7
9
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10,M=20;
int n,m;
int pow_2[M],log_2[N];
int f[N][M]; //f[i][j]:区间[i,i+2^j-1]内的最值
void Init()
{
for(int i=0;i<M;i++) pow_2[i]=1<<i;
log_2[0]=-1;
for(int i=1;i<=n;i++) log_2[i]=log_2[i/2]+1;
}
void get_ST()
{
for(int i=1;i<=log_2[n];i++) //先枚举区间长度
for(int j=1;j+pow_2[i]-1<=n;j++)
f[j][i]=max(f[j][i-1],f[j+pow_2[i-1]][i-1]);
}
int query(int l,int r)
{
int k=log_2[r-l+1];
return max(f[l][k],f[r-pow_2[k]+1][k]);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&f[i][0]);
Init();
get_ST();
int l,r;
while(m--)
{
scanf("%d%d",&l,&r);
printf("%d\n",query(l,r));
}
return 0;
}
例题2-2:The Water Problem - HDU - 5443
题目:
水是非常有限的资源。人们总是争取最大的水资源。给定一系列具有 a 1 , a 2 , a 3 , . . . , a n a_1,a_2,a_3,...,a_n a1,a2,a3,...,an的水资源,每个数表示水资源的大小。给定一组查询,每个查询包含2个整数 l l l 和 r r r ,请找出 l l l 和 r r r 之间最大的水源。(包含l和r)
Input
首先给出一个整数 T ( T ≤ 10 ) T(T≤10) T(T≤10),表示测试用例的数量。对于每个测试用例,在表示水源数量的行上有 n ( 0 ≤ n ≤ 1000 ) n(0≤n≤1000) n(0≤n≤1000)。 n n n 个整数分别表示 a 1 , a 2 , a 3 , . . . , a n a_1,a_2,a_3,...,a_n a1,a2,a3,...,an, 1 ≤ a i ≤ 1 0 6 1≤a_i≤10^6 1≤ai≤106。在下一行,有一个数字 q ( 0 ≤ q ≤ 1000 ) q(0≤q≤1000) q(0≤q≤1000)表示查询的数量。之后,将有 q q q行,其中两个整数 l l l 和 r ( 1 ≤ l ≤ r ≤ n ) r(1≤l≤r≤n) r(1≤l≤r≤n)表示您应该找到最大水源的范围。
Output
对于每次查询,输出查询区间中最大的水资源。
Sample Input
3
1
100
1
1 1
5
1 2 3 4 5
5
1 2
1 3
2 4
3 4
3 5
3
1 999999 1
4
1 1
1 2
2 3
3 3
Sample Output
100
2
3
4
4
5
1
999999
999999
1
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e3+10,M=15;
int n,m,T;
int pow_2[M],log_2[N];
int f[N][M]; //f[i][j]:区间[i,i+2^j-1]内的最值
void Init()
{
for(int i=0;i<M;i++) pow_2[i]=1<<i;
log_2[0]=-1;
for(int i=1;i<N;i++) log_2[i]=log_2[i/2]+1;
}
void get_ST()
{
for(int i=1;i<=log_2[n];i++) //先枚举区间长度
for(int j=1;j+pow_2[i]-1<=n;j++)
f[j][i]=max(f[j][i-1],f[j+pow_2[i-1]][i-1]);
}
int query(int l,int r)
{
int k=log_2[r-l+1];
return max(f[l][k],f[r-pow_2[k]+1][k]);
}
int main()
{
Init();
cin>>T;
while(T--)
{
memset(f,0,sizeof f);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&f[i][0]);
get_ST();
scanf("%d",&m);
int l,r;
while(m--)
{
scanf("%d%d",&l,&r);
printf("%d\n",query(l,r));
}
}
return 0;
}