原题是P3865
ST表是一种数据结构
ST表类似于树状数组和线段数的数据结构,能够快速访问一定区间内的最大值/最小值的值(解决RMQ问题:Range Minimum/Maximum Query,即区间最值查询)问题的离线算法。
其预处理的时间复杂度与线段树,树状数组相同,为O(n*logn)。
但ST表查询的时间复杂度为O(1),而线段树和树状数组查询的时间复杂度都为O(logn)。是不是更加优秀了呢!
首先我们来表示一下(以储存最大值为例):
void do_it(){ for(int i=1;i<=n;i++) st[i][0]=a[i]; for(int i=1;(1<<i)<=n;i++){ for(int j=1;j+(1<<i)-1<=n;j++){ st[j][i]=max(st[j][i-1],st[j+(1<<(i-1))][i-1]); } } return ; }
首先我们需要说明一下,st[i][j]的意义表示从i开始向右数pow(2,j)位中的最大值
由于我们在内层循环中有判断j+(1<<i)-1<=n条件,所以一定会有i+pow(2,j)-1<=n
所以我们很容易得到st[i][j]一定是合法的范围内的
然后我们需要证明一个很简单的定理:
当我们将log2x向下取整时,有2log2x >x/2 ①
来证明一下:
因为我们将log2x向下取整,假定x==pow(2,n),则log2x==n
所以我们有当pow(2,n)<= x < pow(2,n+1)时的log2x均为n
所以就有2log2x == pow(2,n),因为易得有pow(2,n)>pow(2,n+1)/2
因为x<pow(2,n+1)
所以有2log2x > x/2
QED.
然后我们现在还要考虑如何求得向下取整的log2x 这一问题
1.通过对数运算法则logab==logxb/logxa,以及调用cmath库中的log函数实现
x=log(b)/log(a)
(x是我们要求的logab)
优点:方便
缺点:当查询的次数很多时,很有可能使得时间复杂度大大增加
2.预处理得到范围内的所有logn
优点:访问方便且访问的时间复杂度为O(1)
缺点:访问次数很少是预处理会增加很大的时间复杂度
但是我们知道预处理的时间复杂度其实也是O(1)的,只不过只是一个很大的常数罢了,只要在范围内就不会TLE
那么我们如何范围内一定范围内的最大值呢?
假设我们要求i到x的最大值
因为我们已经有定理①,所以当我们要求i到i+pow(2,j)-1范围内的最大值时,我们只需要求i到i+pow(2,j-1)-1的最大值和x-pow(2,j-1)+1到x范围内的最大值并且取两者中的较大值就可以了!
AC代码:
#include<cctype> #include<iostream> #include<cstdio> using namespace std; const int N = 100010; int lg[N]; int n,m; int a[N]; int st[N][20]; int l,r; int read() { int x = 1,a = 0; char ch = getchar(); while(ch < '0' || ch > '9'){//如果读进来的不是数字…… if(ch == '-')x = -1;//判断负号 ch = getchar(); } while(ch <= '9'&&ch >= '0'){//如果读进来的是数字…… a = a * 10 + ch - '0'; ch = getchar(); } return x*a; } void init(){ lg[1]=0; for(int i=2;i<=n;i++){ lg[i]=lg[i/2]+1; } return ; } void do_it(){ for(int i=1;i<=n;i++) st[i][0]=a[i]; for(int i=1;(1<<i)<=n;i++){ for(int j=1;j+(1<<i)-1<=n;j++){ st[j][i]=max(st[j][i-1],st[j+(1<<(i-1))][i-1]); } } return ; } int main(){ n=read(); m=read(); for(int i=1;i<=n;i++){ a[i]=read(); } init(); do_it(); for(int i=1;i<=m;i++){ l=read(); r=read(); printf("%d ",max(st[l][lg[r-l+1]],st[r-(1<<lg[r-l+1])+1][lg[r-l+1]])); } return 0; }