WBLT 大学习
前言
本文大量参考 \(OI-Wiki\)。
\(WBLT\) 可持久化,是一种 \(Leafy\) \(Tree\)(只有叶子节点储存原始信息)。
平衡方法
空间复杂度 \(O(n)\),要及时空间回收。
定义一个非叶点的平衡度为
其中 \(siz\) 为其子树中叶子节点的数目。
当树的每个节点 \(x\) 有 \(\rho(x) \ge \alpha\) 时,我们称这颗树是 \(\alpha\)-平衡 的,我们通过维护整棵树保持 \(\alpha\)-平衡 来平衡复杂度。
当其失衡时,通过旋转保持平衡。
当 \(w\) 的权值大于 \(\beta\) 时,单旋无法平衡复杂度,这时需要双旋。
然后论文给出平衡系数,相当于取 \(\alpha = \frac{1}{4},\beta = \frac{2}{3}\)。
因此:
- 当 \(x>3y\) 时,判断失衡。
- 当 \(w\le 2z\) 时,选择单旋,否则,选择双旋。
参考实现:
void rot(int &p,bool f){
int A,B,C,D;
tie(A,B)=Cut(p);
if(!f)tie(C,D)=Cut(A),p=Link(C,Link(D,B));
else tie(C,D)=Cut(B),p=Link(Link(A,C),D);
}
void bl(int &p){
bool f=siz[ch[p][1]]>siz[ch[p][0]];
if(siz[ch[p][f]]<=siz[ch[p][!f]]*3)return ;
int x=ch[p][f];
if(siz[ch[x][f^1]]>siz[ch[x][f]]*2)rot(x,f^1);
rot(p,f);
}
\(f=0\) 时为左旋,\(f=1\) 时为右旋.
可开始时插入极大值,避免空树判断。
附 【模板】普通平衡树(数据加强版) 代码:
点击查看代码
#include<bits/stdc++.h>
#define mp make_pair
#define pb push_back
#define rpt(a,b,c) for(int a=b;a<=c;a++)
#define tpr(a,b,c) for(int a=b;a>=c;a--)
typedef long long LL;
using namespace std;
const int N=2.2e6+10;
namespace WBLT{
int rt;
int ch[N][2],siz[N],val[N];
int stk[N],cntn;
int n,a[N];
int New(){
int p=stk[0]?stk[stk[0]--]:++cntn;
siz[p]=ch[p][0]=ch[p][1]=val[p]=0;
return p;
}
void Del(int &p){stk[++stk[0]]=p,p=0;}
int Copy(int x){
int p=New();
ch[p][0]=ch[x][0],ch[p][1]=ch[x][1];
val[p]=val[x],siz[p]=siz[x];
return p;
}
void pu(int p){
siz[p]=siz[ch[p][0]]+siz[ch[p][1]];
val[p]=val[ch[p][1]];
}
int New(int v){
int p=New();
val[p]=v,siz[p]=1;
return p;
}
int Link(int x,int y){
int p=New();
ch[p][0]=x,ch[p][1]=y;
return pu(p),p;
}
auto Cut(int &p){
int x=ch[p][0],y=ch[p][1]; Del(p);
return mp(x,y);
}
void rot(int &p,bool f){
int A,B,C,D;
tie(A,B)=Cut(p);
if(!f)tie(C,D)=Cut(A),p=Link(C,Link(D,B));
else tie(C,D)=Cut(B),p=Link(Link(A,C),D);
}
void bl(int &p){
bool f=siz[ch[p][1]]>siz[ch[p][0]];
if(siz[ch[p][f]]>siz[ch[p][!f]]*3){
int x=ch[p][f];
if(siz[ch[x][f^1]]>siz[ch[x][f]]*2)rot(x,f^1);
rot(p,f);
}
}
int build(int l=1,int r=n){
if(l==r)return New(a[l]);
int mid=(l+r)>>1;
return Link(build(l,mid),build(mid+1,r));
}
int ins(int p,int v){
if(siz[p]==1){
int x=p,y=New(v);
if(val[y]<val[x])swap(x,y);
return Link(x,y);
}
bool f=v>val[ch[p][0]];
ch[p][f]=ins(ch[p][f],v);
return pu(p),bl(p),p;
}
int del(int p,int v){
if(siz[p]==1)return p;
bool f=v>val[ch[p][0]];
if(siz[ch[p][f]]==1){
if(val[ch[p][f]]==v)return ch[p][f^1];
return p;
}
ch[p][f]=del(ch[p][f],v);
return pu(p),bl(p),p;
}
int getrk(int p,int v){
if(siz[p]==1)return 1;
if(v>val[ch[p][0]])return siz[ch[p][0]]+getrk(ch[p][1],v);
return getrk(ch[p][0],v);
}
int getnum(int p,int k){
if(siz[p]==1)return val[p];
int dt=siz[ch[p][0]];
if(k>dt)return getnum(ch[p][1],k-dt);
return getnum(ch[p][0],k);
}
int getpre(int p,int v){int dt=getrk(p,v);return dt>1?getnum(p,dt-1):-INT_MAX;}
int getnxt(int p,int v){return getnum(p,getrk(p,v+1));}
void out(int p){
if(siz[p]==1)return printf("%d ",val[p]),void();
rpt(i,0,1)out(ch[p][i]);
}
#undef N
}using namespace WBLT;
int m,v,op,x,las,ans;
int main(){
scanf("%d%d",&n,&m);
rpt(i,1,n)scanf("%d",&a[i]);
sort(a+1,a+n+1),rt=build(),ins(rt,INT_MAX);
rpt(i,1,m){
scanf("%d%d",&op,&x),x^=las;
if(op==1)rt=ins(rt,x);
else if(op==2)rt=del(rt,x);
else if(op==3)ans^=(las=getrk(rt,x));
else if(op==4)ans^=(las=getnum(rt,x));
else if(op==5)ans^=(las=getpre(rt,x));
else ans^=(las=getnxt(rt,x));
}
printf("%d",ans);
return 0;
}
合并
合并直接 \(Link\) 后,跑平衡函数不对,考虑极端情况(一个节点与一个大量节点满二叉树合并)可知。
因此当 \(x,y\) 直接合并不平衡时,设情况为上述图片,处理方式类似旋转,将 \(w\) 与 \(y\) 进行 \(Link\),然后跑平衡函数。
参考实现:
int Merge(int x,int y){
if(!x||!y)return x|y;
int A,B,C;
if(siz[x]>siz[y]*3){
pd(x),tie(A,B)=Cut(x);
C=Link(A,Merge(B,y));
return C;
}
if(siz[x]*3<siz[y]){
pd(y),tie(A,B)=Cut(y);
C=Link(Merge(x,A),B);
return bl(C),C;
}
return Link(x,y);
}
合并的时间复杂度为 \(O(log(\frac{x}{y}))\)。
分裂
分裂时找到相应的 \(log(n)\) 段区间,直接合并时间复杂度就是对的。
参考实现:
void Split(int p,int k,int &x,int &y){
if(siz[p]==1)return x=0,y=p,void(); pd(p);
int dt=siz[ch[p][0]];
if(k>=dt)Split(ch[p][1],k-dt,x,y),x=Merge(ch[p][0],x);
else Split(ch[p][0],k,x,y),y=Merge(y,ch[p][1]);
}
可持久化
将回收空间去掉,并在 插入/删除 时将路径复制即可。
懒标记处理
在下传标记时复制两个儿子即可。
事实是:空间复杂度不会大于时间复杂度。
由于 \(WBLT\) 的合并时间复杂度正确。
因此在下传懒标记时复制两个儿子时空复杂度正确。
有一个优化为记录当前节点父亲个数,若为一时直接修改即可,但我不会维护(
附 【模板】可持久化文艺平衡树 代码:
点击查看代码
#include<bits/stdc++.h>
#define mp make_pair
#define pb push_back
#define rpt(a,b,c) for(int a=b;a<=c;a++)
#define tpr(a,b,c) for(int a=b;a>=c;a--)
typedef long long LL;
using namespace std;
const int N=4e5+10;
namespace WBLT{
int rt[N];
#define N N<<5
int ch[N][2],siz[N];
LL sum[N]; int lz[N];
int stk[N],cntn;
int New(){
int p=stk[0]?stk[stk[0]--]:++cntn;
siz[p]=ch[p][0]=ch[p][1]=sum[p]=lz[p]=0;
return p;
}
int Copy(int x){
int p=New();
ch[p][0]=ch[x][0],ch[p][1]=ch[x][1];
sum[p]=sum[x],siz[p]=siz[x],lz[p]=lz[x];
return p;
}
void pu(int p){
siz[p]=siz[ch[p][0]]+siz[ch[p][1]];
sum[p]=sum[ch[p][0]]+sum[ch[p][1]];
}
void upd(int p){lz[p]^=1,swap(ch[p][0],ch[p][1]);}
void pd(int p){
if(lz[p]){
int &x=ch[p][0],&y=ch[p][1];
x=Copy(x),y=Copy(y);
upd(x),upd(y),lz[p]=0;
}
}
int New(int v){
int p=New();
sum[p]=v,siz[p]=1;
return p;
}
int Link(int x,int y){
int p=New();
ch[p][0]=x,ch[p][1]=y;
return pu(p),p;
}
auto Cut(int &p){
int x=ch[p][0],y=ch[p][1];
return mp(x,y);
}
void rot(int &p,bool f){
int A,B,C,D;
tie(A,B)=Cut(p);
if(!f)tie(C,D)=Cut(A),p=Link(C,Link(D,B));
else tie(C,D)=Cut(B),p=Link(Link(A,C),D);
}
void bl(int &p){
pd(p); bool f=siz[ch[p][1]]>siz[ch[p][0]];
if(siz[ch[p][f]]<=siz[ch[p][!f]]*3)return ;
int x=ch[p][f]; pd(x);
if(siz[ch[x][f^1]]>siz[ch[x][f]]*2)rot(x,f^1);
rot(p,f);
}
int ins(int p,int k,int v){
if(siz[p]==1){
int x=New(v),y=p;
return Link(x,y);
}
pd(p);
int t=Copy(p),dt=siz[ch[p][0]];
bool f=k>=dt;
ch[t][f]=ins(ch[p][f],k-dt*f,v);
return pu(t),bl(t),t;
}
int del(int p,int k){
if(siz[p]==1)return p;
pd(p);
int dt=siz[ch[p][0]];
bool f=k>dt;
if(siz[ch[p][f]]==1)return ch[p][f^1];
int t=Copy(p);
ch[t][f]=del(ch[p][f],k-f*dt);
return pu(t),bl(t),t;
}
int Merge(int x,int y){
if(!x||!y)return x|y;
int A,B,C;
if(siz[x]>siz[y]*3){
pd(x),tie(A,B)=Cut(x);
C=Link(A,Merge(B,y));
return C;
}
if(siz[x]*3<siz[y]){
pd(y),tie(A,B)=Cut(y);
C=Link(Merge(x,A),B);
return bl(C),C;
}
return Link(x,y);
}
void Split(int p,int k,int &x,int &y){
if(siz[p]==1)return x=0,y=p,void(); pd(p);
int dt=siz[ch[p][0]];
if(k>=dt)Split(ch[p][1],k-dt,x,y),x=Merge(ch[p][0],x);
else Split(ch[p][0],k,x,y),y=Merge(y,ch[p][1]);
}
LL qry(int L,int R,int p,int l,int r){
if(R>=r&&l>=L)return sum[p]; pd(p);
int mid=l+siz[ch[p][0]]-1;
if(mid>=R)return qry(L,R,ch[p][0],l,mid);
if(mid<L)return qry(L,R,ch[p][1],mid+1,r);
return qry(L,R,ch[p][0],l,mid)+qry(L,R,ch[p][1],mid+1,r);
}
void modrev(int &p,int l,int r){
int x,y,z;
Split(p,r,y,z),Split(y,l-1,x,y);
y=Copy(y),upd(y),p=Merge(Merge(x,y),z);
}
#undef N
}using namespace WBLT;
int m,v,op;
LL l,r,las;
int main(){
rt[0]=New(0);
scanf("%d",&m);
rpt(i,1,m){
scanf("%d%d%lld",&v,&op,&l);
if(op!=2)scanf("%lld",&r);
rt[i]=rt[v],l^=las,r^=las;
if(op==1)rt[i]=ins(rt[i],l,r);
else if(op==2)rt[i]=del(rt[i],l);
else if(op==3)modrev(rt[i],l,r);
else printf("%lld\n",las=qry(l,r,rt[i],1,siz[rt[i]]));
}
return 0;
}
合并平衡
可以发现我们在合并时可以不用平衡函数。
在合并 \(w\) 和 \(y\),判断合并后是否平衡,若不平衡将 \(z,u\),\(v,y\) 分别合并后 合并。
平衡函数就可以写将左右儿子合并,这样就可以不用写平衡函数和旋转函数。
参考实现:
int Merge(int x,int y){
if(!x||!y)return x|y;
int A,B,C,D;
if(siz[x]>siz[y]*3){
tie(A,B)=Cut(x);
if(siz[B]+siz[y]>siz[A]*3){
tie(C,D)=Cut(B);
return Merge(Merge(A,C),Merge(D,y));
}
return Link(A,Merge(B,y));
}
if(siz[y]>siz[x]*3){
tie(A,B)=Cut(y);
if(siz[x]+siz[A]>siz[B]*3){
tie(C,D)=Cut(A);
return Merge(Merge(x,C),Merge(D,B));
}
return Link(Merge(x,A),B);
}
return Link(x,y);
}
void bl(int &p){int x=Merge(ls[p],rs[p]);Del(p),p=x;}