[笔记]ST表
[笔记]ST表
原题链
算法用途
ST表主要用于解决RMQ问题(区间最值问题),可以做到\(O(nlogn)\)预处理,\(O(1)\)询问
算法描述
ST表利用的是倍增的思想,以求区间最大值为例,我们用\(Max[i][j]表示\)从i位置开始的\(2^j\)个数中的最大值,例如\(Max[i][1]\)表示的是\(i\)位置到第\(2^1 - 1\)个数,也就是第\(i\)个数的下一个数,这两个数的最大值.在转移的时候为我们考虑一个\(max\)操作的性质:\(max(a,b,c) = max(max(a,b),max(b,c))\)从这个性质我们发现,我们可以由两个较小的,用重叠的区间来推出一个大区间,因此我们可以少维护一些区间.
知道上面的性质后,转移的式子就很好理解了.首先初始化的时候\(f[i][0] = a[i]\)这表示从第\(i\)个数往后\(2^0 - 1 = 0\)个数也就是\(i\)这个数本身的值,所以直接赋值就好.接着对于包含几个数的区间的转移方程是\(f[i][j]=max(f[i][j-1],f[i + 2^{j-1}][j-1])\)这个式子是由上面的性质推出来的,首先我们假设\(i = 1\)这样方便计算与描述,那么根据上面的性质,我们可以将从第\(1\)个元素到第\(2^j - 1\)个元素的区间划分成从第\(1\)个元素到第\(2^{j-1}个\)元素的区间以及从第\(2^{j-1} + 1\)个元素到第\(j\)个元素这两个区间分更新最大值,这样就可以从小的区间推出大的区间,这也是符合上面讲的性质的.
再来说查询,查询的时候会遇到很多问题,如果我们只从左端点去找,那么如果是要查询\((1,7)\)的最小值怎么办呢?如果从\(1\)往后走\(2^2\),就只能查询\((1,3)\),但如果往后走\(2^3\),又查询的是\((1,8)\),所以并不好做.为了解决这个问题,我们可以直接从查询区间的两端一起找,现将区间的长度写成\(2^k\),接着从两端\(l,r\)分别找,找的区间重叠了也没有关系,用代码写这一段就是:
int ask(int x,int y){//查询
int k = log2(y - x + 1 );
return max(maxx[x][k],maxx[y - (1 << k) + 1][k]);
}
但有一点要注意,在从右端点查询的时候为什么要\(+1\)呢?可以通过举例子来理解,比方说我们现在要查询的区间是\((1,8)\),那么\(k=3\),从左端点查就是查\((1,7)\),从右端点查如果不\(+1\)的话,就是查\((0,6)\),所以是要\(+1\)的.再来严谨的讲一下,实际上,从右端点查询就是要找到一个点满足\(x + 2^k - 1 = r\),移项之后就是\(x = r - 2^k + 1\).
代码
#include <bits/stdc++.h>
using namespace std;
int maxx[1000010][50];
int ask(int x,int y){//查询
int k = log2(y - x + 1 );
return max(maxx[x][k],maxx[y - (1 << k) + 1][k]);
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++){
scanf("%d",&maxx[i][0]);
}
for(int i = 1;i <= 21;i++){//预处理
for(int j = 1;j + (1 << i) - 1 <= n;j++){
maxx[j][i] = max(maxx[j][i - 1],maxx[j + (1 << (i - 1))][i - 1]);
}
}
for(int i = 1;i <= m;i++){
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",ask(l,r));
}
return 0;
}
另:
要注意在洛谷提交的时候一定要用\(scanf\),不能用\(cin\)否则会超时,同时要注意位运算的优先级,所以在预处理的时候那个括号很有讲究的(位运算的优先级是低于普通加减法的)