比赛的时候写了个线段树......然后tle了...不知道世界上还有st表这种东西(大哭
预处理复杂度 | 查询m次复杂度 | |
线段树 | o(n) | o(mlogn) |
st表 | o(nlogn) | o(m) |
不过st表空间复杂度不如线段树好。
总结:如果只需要查询就用st表,但是st表无法增删改,所以需要多次修改值的时候,就用线段树
#include <bits/stdc++.h>
using namespace std;
int n,m;
const int maxn=1e5;
int a[maxn], st[maxn][20];
void create(){
int k=log2(n);
for(int j=1;j<=k;j++){//列数一共有log2n列
for(int i=1;i+(1<<j)-1<=n;i++){//每一列的长度都不一样,最大只能达到i+(2^j)-1
st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
//当前st的值,是它左边一个st(左边界-中数)
//和下面第2^(j-1)个st(中数-右边界)中的最大值
}
}
//st[i][j]含义:数字下标i到数字下标i+(2^j)-1区间的最大值
//即每个数字往后2^j(包含自身)区间内的最大值
}
int result(int L,int R){
int k=log2(R-L+1);
return max(st[L][k],st[R-(1<<k)+1][k]);
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>st[i][0];//根据上文st表含义,[i][0]的范围是i~i,即本身
}
create();//建表
cin>>m;
while(m--){
int L,R;
cin>>L>>R;
cout<<result(L,R)<<endl;
}
}
关于建表的注释都写在代码里了。
查询原理:
因为我们要查询的区间大小不一定正好是2的j次方,所以不一定能直接在表中找到对应的最大值。但是如果我们要查询的区间是[l,r],那我们可以查询max(区间[l,a],区间[b,r]),并且a一定要大于等于b,因为中间有交集的部分不影响最大值,但如果中间有断裂的地方就影响了,如果最大值刚好在断裂的区间里就会判断错误。
也就是说,我们可以选择区间[l,l+2^x]和[r-2^x,r]这两段区间,因为他们的差值都为2的次方,一定能在表中找到。
那如何才能保证两端区间相重合呢?我们只需要找到x的最大值就可以了,也就是让这两个区间各自都截取最长,即2^x<r-l+1,x<log2(r-l+1),x取最大值k。
此时左st表的下标就是st[l][k],右st表st[r-(1<<k)+1][k],分别代表了从l到l后2^k个数的区间,和从r到r前2^k个数的区间,计算两者最大值即可。