ST表
ST表
由于线段树的太杂了而且之前太懒了,所以说先一放
ST表这里有一个O(n)-O(1)RMQ这里先放着因为意义不大
st表是一种静态的数据结构,用来维护一些区间问题,只支持在线查询,不支持修改
查询单次\(O(1)\)
预处理\(O(nlogn)\)
另外st表的使用限制是维护的东西必须是可重复贡献问题
可重复贡献问题是对于运算op
有x op x=x
如\(max/min\)
\(max(a,a)=a\)
按位& |
a&a=a
\(a|a=a\)
还有一个非常重要的\(gcd(a,a)=a\)
对于区间和
即+
\(a+a=2a!=a\)
不能用st表维护
st表基于倍增和dp思想
空间复杂度\(O(nlogn)\)
时间复杂度\(O(nlogn +m)\)
这里以区间max为例
设\(f[i][j]\)表示\([ i,i+2^j -1]\)的最大值
那么有如下dp方程
\(f[i][j]=max(f[i][j-1],f[i+2^{(j-1)}][j-1])\)
怎么理解呢
\(f[i][j]=max[ i,i+2^j -1]\)
\(f[i][j-1]=max[ i,i+ 2^(j-1) -1]\)
f[i+2(j-1)][j-1]=max[i+2,i+2^j -1]
两个取max刚好覆盖了整个区间
那么边界呢
即直接运用数组a的边界
\(f[i][0]=max[i,i+2^0 -1]=max[i,i]\)
所以\(f[i][0]=a[i]\)
这是边界
然后原则上\(i\)\(j\)内外顺序无所谓
但一般\(i\)外\(j\)内
计算\(f[i][j]\)依赖于\(j-1\),依赖于更大的\(i\),所以\(i\)从\(n\)到\(1\)枚举,所以\(j\)从小到大枚举(从\(1\)开始,当\(i+2^j -1 >n\)时停止)
然后直接计算
注意计算前先初始化,让\(f[i][0]=a[i]\)
那么\(i\)最大\(n\),所以第一维度数组开到\(n\)
而\(j\)是最大\(log2n\)
因为是\(2^j\)
所以一般第二维度开到\(log2n\)向上取整 \(+2\) 左右
计算\(f\)后
就要处理查询操作
查询操作可以理解为在线的
查询\(l,r\)的最大值
可以把 \(l,r\)拆成两部分
中间有重叠没关系
(可重复贡献问题)
\(max([l,l+2^k -1],[r-2^k +1,r])\)
\(k=log2(r-l+1)\) 向下取整
现在要证明
1.\(l+2^k-1>=l\)
2.\(l+2^k-1 >=r-2^k+1\)
3.\(r-2^k+1<=r\)
4.\(l+2^k -1 <=r\)
5.\(r-2^k +1 >=l\)
首先若\(k=log2(r-l+1)\)
那么\([l,l+2^k -1]=[l,l+r-l+1-1]=[l,r]\)
\([r-2^k +1,r]=[r-r+l-1+1,r]=[l,r]\)
没有问题
那么向下取整后\(k\)最多与标准值差\(1\)
则\(2^k1=2^{(k−1)} =2^k/2=(r−l+1)/2\)
\(l+(r−l+1)/2−1=floor((l+r−1)/2)\)
可知此时为基本接近于区间中点
那么肯定\(>=l\) && \(<=r\)
对边界分析
1.\(l==r\)
\((l+r−1)/2=(2l−1)/2\)
因为\(2l\)为偶数
所以\(2l-1\)为奇数
所以\((2l-1)/2=l-1\)
好像有问题
但是你想当\(l==r\)时
\(k=log_21=0\)
\(l+2^0 -1=l\)
所以
没有问题
2.\(l\)和\(r\)相差\(1\)
\((l+r−1)/2=(l+l+1−1)/2=l\)
没有问题
其他情况\(l\)和\(r\)之间至少有一个值
所以
1.\(l+2^k-1>=l\)
4.\(l+2^k -1 <=r\)成立
\(r-2^k +1=r−(r−l+1)/2+1=(r+l+1)/2\)
所以
2.\(l+2^k-1 >=r-2^k+1\)成立
而\((r+l+1)/2\)也接近区间中点
因此肯定\(>=l\) \(<=r\)
考虑边界
1.\(l==r\)
\((r+l+1)/2=(2l+1)/2=l\)
2.\(r=l+1\)
\((r+l+1)/2=(2l+2)/2=l+1\)
所以正确性保证了
那么怎么用\(f\)数组呢
\([l,l+2^k -1]\)
相当于
\(f(l ,k)\)
很显然
\([r-2^k +1,r]\)
\(f(i,j)=max[i,i+2^j -1]\)
所以\(r-2^k +1 = i\)
\(i + 2^j -1 =r\)
\(r-2^k+1 + 2^j -1=r-2^k+2^j\)
\(r-2^k +2^j =r\)
\(2^j=2^k\)
\(j=k\)
所以\(f[r-2^k+1][k]\)
最后两个答案取\(max\)即可
应用1
区间gcd的单次查询是\(O(logw)\) \(w\)是值域
区间gcd预处理复杂度为\(O(nlogn +nlogw)\)
而不是\(O(nlognlogw)\)
应用2
区间加,区间gcd,\(gcd(a,b)=gcd(a,b-a)\)
\(gcd(a,b,c)=gcd(a,b-a,c-b)\)
因此可以维护区间差分,区间gcd,变成了单点修改
固定左端点,则所有右端点构成的区间的本质不同的区间gcd只有\(logw\)个
\(w\)是值域
特殊优化的ST表
另外还有一个问题,就是\(m\)不大,或保证询问随机时,\(n\)却很大
即预处理若\(nlogn\)会超时
那么可以通过增大单次询问复杂度,保证预处理\(O(n)\)
就是把st表与分块结合
块长\(log2n\)
分为\(n/log2n\)个块
先花\(O(n)\)预处理出每一块内的前缀后缀最大值
然后对每一个块为整体进行st表预处理
\(O(n/logn*(log(n/logn)))\)
\(=O(n/logn*(logn - loglogn))\)
\(=O(n-nloglogn/logn)\)
也是接近\(O(n)\)
的
然后询问
询问时如果不在一个块内
如果两个块相邻,那么直接输出\(l\)的后缀max和\(r\)的前缀max 的max
否则
\(l\)所在块\(+1\),\(r\)所在块\(-1\) 的区间max 与\(l\)的后缀max 与 \(r\)的前缀max 的max
\(O(1)\)
如果在一个块内
如果\(l\)是块起点
则输出\(r\)的前缀max
如果\(r\)是块终点
则输出\(l\)的后缀max
否则:
如果\(l-1\)的前缀max和\(r\)的前缀max不同,说明\(first~l-1\) \(first~r\) 的max不同
输出\(r\)的前缀max
如果\(r+1\)和\(l\)的后缀max不同,则输出\(l\)的后缀max
其他情况暴力查找\(O(logn)\)
所以只有数据随机时才能保证单次查询\(O(1)\)
否则可能被极端数据卡到\(O(logn)\)
//#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define re register
inline int reads(){
char s=getchar();
int f=1,num=0;
while(s<'0'||s>'9'){
if(s=='-'){
f=-1;
s=getchar();
break;
}
s=getchar();
}
while(s>='0'&&s<='9'){
num=num*10+s-48;
s=getchar();
}
return num*f;
}
inline void writes(int x){
if(x<0)x=-x,putchar('-');
if(x<10){
putchar(x+48);
return;
}
writes(x/10);
putchar(x%10 + 48);
}
int a[100010],kuai[100010],premax[100010],lastmax[100010],st[100010][18],logs2[100010];
struct kuais{
int l,r;
}prelast[100010];
int main(){
int n=reads(),m=reads();
for(re int i=1;i<=n;++i)a[i]=reads();
int len_kuai=floor(log2(n)),num_kuai=ceil((double)n/len_kuai);
for(re int i=1,k=1,x=1;i<=n;++i,++x){
if(x==1)prelast[k].l=i;
kuai[i]=k;
if(x==len_kuai||i==n)prelast[k].r=i;
if(x==len_kuai)x=0,++k;
}
for(re int i=1,x=1;i<=n;++i,++x){
if(x==1)premax[i]=a[i];
else{
premax[i]=max(premax[i-1],a[i]);
}
if(x==len_kuai)x=0;
}
for(re int i=n;i>=1;--i){
if(i==prelast[kuai[i]].r )lastmax[i]=a[i];
else{
lastmax[i]=max(lastmax[i+1],a[i]);
}
}
for(re int i=1;i<=num_kuai;++i){
st[i][0]=premax[prelast[i].r ];
}
for(re int i=num_kuai;i>=1;--i){
for(re int j=1;i+(1<<j)-1<=num_kuai;++j){
st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
}
logs2[1]=0;
for(re int i=2;i<=n+2;++i){
logs2[i]=logs2[i/2]+1;
}
while(m--){
int l=reads(),r=reads();
if(kuai[l]!=kuai[r]){
if(kuai[r]-kuai[l]>1){
int lin2=logs2[kuai[r]-kuai[l]-1 ];
int lin1=max(st[kuai[l]+1][lin2],st[kuai[r]-(1<<lin2)][lin2]);
writes(max(premax[r],max(lastmax[l],lin1)));
putchar('\n');
}else{
writes(max(lastmax[l],premax[r]));
putchar('\n');
}
}else{
if(l==prelast[kuai[l]].l ){
writes(premax[r]);
putchar('\n');
}else if(r==prelast[kuai[r]].r ){
writes(lastmax[l]);
putchar('\n');
}else {
if(premax[l-1]!=premax[r]){
writes(premax[r]);
putchar('\n');
continue;
}else if(lastmax[l]!=lastmax[r+1]){
writes(lastmax[l]);
putchar('\n');
continue;
}
int ans=-1e9;
for(re int i=l;i<=r;++i){
ans=max(ans,a[i]);
}
writes(ans);
putchar('\n');
}
}
}
return 0;
}
黄粱一梦,终是一空
本文来自博客园,作者:hicode002,转载请注明原文链接:https://www.cnblogs.com/hicode002/p/19532823

浙公网安备 33010602011771号