某数据结构

前言

如果撞了国外论文,希望指出(已经撞集训队论文了)

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\)内查询,之后余下的块便递归处理。

如图

无标题.png

4.复杂度证明

- 前置定理

(1)若\(n^{\frac{1}{2^k}}=2\),则\(k≈log_2log_2n\)

\[n^{\frac{1}{2^k}}=2\\ \Leftrightarrow n=2^{2^k}\\ \Leftrightarrow 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年集训队论文:)

posted @ 2020-12-04 19:02  ファイナル  阅读(237)  评论(7编辑  收藏  举报