WBLT 大学习

前言

本文大量参考 \(OI-Wiki\)
\(WBLT\) 可持久化,是一种 \(Leafy\) \(Tree\)(只有叶子节点储存原始信息)。

平衡方法

空间复杂度 \(O(n)\),要及时空间回收。
定义一个非叶点的平衡度为

\[\rho(x)=\frac{\min{( siz(ls(x)),siz(rs(x)) )}}{siz(x)} \]

其中 \(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;}

参考博客:
\(OI-Wiki\)
从 Leafy-Tree 到 WBLT

posted @ 2025-09-17 22:36  C_Wish  阅读(21)  评论(0)    收藏  举报