某数据结构
前言
如果撞了国外论文,希望指出(已经撞集训队论文了)
1.功用
简而言之,不带修的线段树/优化版的ST表。
2.时(空)间复杂度
时(空)间复杂度\(O(nlog_2log_2n)\),单次查询\(O(log_2log_2n)\)
3.实现
类比线段树,但我们不是每段区间分为\(2\)段,而是对于区间\([l,r]\),我们划分为大小\(k=\sqrt{r-l+1}\)的块,共\(\lceil\frac{r-l+1}{k}\rceil\)块。
对于每一区间,我们使用二维数组\(f[i][j]\),表示第\(i\)块到第\(j\)块的和(也可以是其他满足结合律(不需要存在逆运算)的运算)。
之后就是查询操作,我们对于整块查询的,在\(f\)内查询,之后余下的块便递归处理。
如图
4.复杂度证明
- 前置定理
(1)若\(n^{\frac{1}{2^k}}=2\),则\(k≈log_2log_2n\)
(2)区间\([l,r]\)的二维数组大小为\(O(r-l)\)
显然
(3)每层空间\(O(n)\)
如上图,显然
(4)树深\(h=O(log_2log_2n)\)
若第\(n\)层某区间大小为\(x\),则第\(n+1\)层最大区间的大小为\(\lceil\sqrt{x}\rceil\)
如深度超过\(1\)层,则倒数第二层的单个节点区间大小为\(2或3\)
\(\therefore n=(((2^2)^2)^2\cdots (h次)),O(h)=O(\log_2\log_2n)\)
(5)树所用空间\(O(n\log_2\log_2n)\)
由(3)(4)易得
(6)单次查询时间复杂度\(O(\log_2\log_2n)\)
首先,对于区间\([l,r]\),它如果与树上当前节点所代表的区间有一侧重合,则至多往下递归一个有一侧的区间,两侧重合则直接返回,无重合则递归两个单侧重合的区间(参考上图)(类比线段树的时间复杂度证明)
由于该树深度为\(O(\log_2\log_2n)\),故时间复杂度\(O(\log_2\log_2n)\)
例题1:区间最大值
(ps:由于常数原因,下代码只能的得92pts,如果有更优的实现可以联系博主)
#include<bits/stdc++.h>
using namespace std;
# define ll long long
# define read read1<ll>()
# define Type template<typename T>
Type T read1(){
T t=0;
char k;
bool vis=0;
do (k=getchar())=='-'&&(vis=1);while('0'>k||k>'9');
while('0'<=k&&k<='9')t=(t<<3)+(t<<1)+(k^'0'),k=getchar();
return vis?-t:t;
}
# define fre(k) freopen(k".in","r",stdin);freopen(k".out","w",stdout)
int s,m;
struct node{
node **x;
int **v;
node(){}
}*root;
int Sq[100005],*sta;
int* New(int x){int *n=sta;sta+=x;return n;}
int Max(const int &a,const int &b){return a>b?a:b;}
int build(node*& n,int l,int r){
n=new node;
int b=Sq[r-l+1],w=(r-l+b)/b;
n->x=new node*[w];n->v=new int*[w];
for(int i=0;i<w;++i)n->v[i]=New(w);
if(l==r)return n->v[0][0]=read;
for(int i=0,j=l;i<w;++i,j+=b)
n->v[i][i]=build(n->x[i],j,min(r,j+b-1));
for(int i=w-1;i>=0;--i)
for(int j=i+1;j<w;++j)
n->v[i][j]=Max(n->v[i][j-1],n->v[j][j]);
return n->v[0][w-1];
}
int query(node* n,int l,int r,int tl,int tr){
tl=Max(l,tl);tr=min(tr,r);
if(tl>tr)return -2e9;
if(l==r)return n->v[0][0];
int b=Sq[r-l+1],w=(r-l+b)/b;
int lx=(tl-l)/b,rx=(tr-l)/b,lf=lx*b+l,rf=(rx+1)*b+l-1;
if(lx==rx)return query(n->x[lx],lf,min(rf,r),tl,tr);
int v=-2e9;
if(lf!=tl)v=query(n->x[lx],lf,lf+b-1,tl,tr);
else --lx;
if(min(rf,r)!=tr)v=Max(v,query(n->x[rx],rf-b+1,min(rf,r),tl,tr));
else ++rx;
if(lx+1!=rx)v=Max(v,n->v[lx+1][rx-1]);
return v;
}
int main(){
s=read,m=read;
sta=new int[s*10];
for(int i=1;i<=s;++i){
int x=Sq[i-1];
Sq[i]=x++;
if(x*x<=i)Sq[i]=x;
}
build(root,1,s);
for(int i=1;i<=m;++i){
int l=read,r=read;
printf("%d\n",query(root,1,s,l,r));
}
return 0;
}
2020.12.27:好像和vanEmdeBoas树的思路很像啊
2021.7.11
可以优化至\(O(n\log\log n+1)\)
我们考虑将区间\([l,r]\)的前缀和后缀存下,然后参考\(ST\)表,每层节点的区间大小为\(2^{2^0},2^{2^1},2^{2^2}...,2^{2^{\log_2\log_2 n}}\)
二维数组还是照常
当然只有这些不行,我们需要\(n=\sum_{i=1}^m2^{2^{x_i}}\),用上面的证明,\(m=O(\log\log n)\)
建\(m\)个这样的表,同样使用二维数组存总和
由于在每个表内的区间查询可以参考\(ST\)表计算得到(前缀和得到边角,二维数组得到中间),所以单次查询时间复杂度\(O(1)\)
由于每个表时空复杂度\(O(Len\log\log Len)\),所以总空间复杂度\(O(n\log\log n+1)\)
好的,撞了2013年集训队论文:)
浮世苍茫,不过瞬逝幻梦
善恶爱诳,皆有定数
于命运之轮中
吞噬于黄泉之冥暗
呜呼,吾乃梦之戍人
幻恋之观者
唯于万华镜中,永世长存