前言
如果撞了國外論文,希望指出(已經撞集訓隊論文了)
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年集訓隊論文:)