WBLT(Weight Balanced Leafy Tree)学习笔记
zhangjianweivv · · 个人记录
CSP-J/S炸掉原地自闭不想写游记、我果然还是太菜、
回来肝专题了,幸好眼疾手快抢了一个比较容易 上头 上手而且看起来很友善的专题——WBLT。
参考资料:
-
王思齐《Leafy Tree 及其实现的加权平衡树》
-
《WBLT 实用入门和讲解》
鸣谢大佬:
-
mzw(帮我普及相关知识)
-
lzy(好心地把这个相对好做的专题让给我了)
本文不是一篇很正经的论文,仅供Leafy Tree入门学习参考。语言表达并不十分严谨,内容也可能存在错漏,如发现问题欢迎指正!
注:以下内容中“有效节点”的意思是这棵树维护的集合中的每个元素对应的点。
一、什么是Leafy Tree以及为什么要学习Leafy Tree
- 对于
Top 操作,删除p[x] ;q[p[x]]=0 ;插入left-1 ;left-- 。p[x]=left ,q[left]=x - 对于
Bottom 操作,删除p[x] ;q[p[x]]=0 ;插入right+1 ;right++ 。p[x]=right ,q[right]=x - 对于
Insert 操作:-
T=0$ -> $continue -
- $T>0$ -> 记$Y=p[x]$的后继; - $T<0$ -> 记$Y=p[x]$的前驱; -
X=p[x]$;$y=q[Y]$;$swap(p[x],p[y])$;$swap(q[X],q[Y])
-
- 对于
Ask 操作,输出p[x] 的排名-1 - 对于
Query 操作,输出q[ 树上排名为x 的位置] (此处的“位置”指的不是书架上的位置,可以理解为“优先级”)
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const double A=1.0/3.0;
const int N=80000+10,inf=0x3f3f3f3f;
struct node{int l,r,c,size;}tr[2*N];
int len,sta[N],tp,left,right;
/*此处省略,WBLT部分同上*/
int Q(int x,int k){return findshuz(1,findrank(1,x)+k);}
int p[3*N],q[3*N];
int main()
{
int n,m,x,id;scanf("%d%d",&n,&m);
left=m;right=n+m+1;vnew(inf);
for(int i=1;i<=n;i++)
{
scanf("%d",&x);id=left+i;
p[x]=id;q[id]=x;add(1,id);
}
while(m--)
{
char s[10];scanf("%s",s);
if(s[0]=='T')
{
scanf("%d",&x);
del(1,0,p[x]);q[p[x]]=0;
add(1,left);p[x]=left;q[left]=x;left--;
}
else if(s[0]=='B')
{
scanf("%d",&x);
del(1,0,p[x]);q[p[x]]=0;
add(1,right);p[x]=right;q[right]=x;right++;
}
else if(s[0]=='I')
{
scanf("%d%d",&x,&id);
if(!id)continue;
int X=p[x],Y=Q(p[x],id),y=q[Y];
swap(p[x],p[y]);swap(q[X],q[Y]);
}
else if(s[0]=='A'){scanf("%d",&x);printf("%d\n",findrank(1,p[x])-1);}
else{scanf("%d",&x);printf("%d\n",q[findshuz(1,x)]);}
}
return 0;
}
4.营业额统计
提交地址:
洛谷P2234 [HNOI2002]营业额统计
caioj2418: 【Leafy Tree】营业额统计
对于每天的最小波动值,我们找到当天的营业额在树上的前驱和后继,比较它和前驱、后继的差的绝对值,更小的那个即为当天的最小波动值。注意要特判没有前驱或者后继的情况,以及别忘了记录以后要把当天的营业额加进树中!
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const double A=1.0/3.0;
const int N=100000+10,inf=0x3f3f3f3f;
struct node{int l,r,c,size;}tr[2*N];
int len,sta[N],tp;
/*此处省略,WBLT部分同上*/
inline int Q(int x){return findshuz(1,findrank(1,x)-1);}
inline int H(int x){return findshuz(1,findrank(1,x));}
int main()
{
int n,ans,x;scanf("%d",&n);
add(vnew(inf),-inf);scanf("%d",&x);add(1,x);ans=x;
for(int i=2;i<=n;i++)
{
scanf("%d",&x);
int q=Q(x),h=H(x);
if(q==-inf)ans+=abs(x-h);
else if(h==inf)ans+=abs(x-q);
else ans+=min(abs(x-q),abs(x-h));
add(1,x);
}
printf("%d\n",ans);
return 0;
}
5.可持久化平衡树
提交地址:
洛谷P3835 【模板】可持久化平衡树
caioj2419: 【Leafy Tree】可持久化平衡树
用WBLT实现可持久化,只需在修改时进行Path Copy——每次要修改一个点时,我们将根到这条点的路径上所有被修改过的点都依次新建一个节点,查找时从不同的根出发即可。
注意空间的大小要开大一点,因为Path Copy还是很占空间的。
- Leafy Tree实现的加权平衡树中,单次操作期望下修改的节点个数很少,且不需要新建冗余的节点。故其内存占用小,常数也小,与其他平衡树(如非旋转Treap)相比有巨大优势。(摘自王思齐《Leafy Tree 及其实现的加权平衡树》)
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const double A=1.0/3.0;
const int N=500000+10;const long long inf=1ll<<31-1ll;
struct node{int l,r,c,size;}tr[25*N];
int len,sta[N],tp;
/*快读*/
/*此处省略,WBLT部分同上*/
int add(int now,int c)
{
if(tr[now].size==1)
{
int Now=vnew(tr[now].c);
tr[Now].l=vnew(min(tr[Now].c,c));
tr[Now].r=vnew(max(tr[Now].c,c));
pushup(Now);return Now;
}
maintain(now);
int l=tr[now].l,r=tr[now].r;
if(c<=tr[l].c)return ins(add(l,c),r);
else return ins(l,add(r,c));
}
int del(int now,int fa,int c)
{
if(tr[now].size==1)return (tr[now].c!=c)?now:-1;
maintain(now);
int l=tr[now].l,r=tr[now].r;
if(c<=tr[l].c)
{
int d=del(l,now,c);
return d==-1?r:ins(d,r);
}
else
{
int d=del(r,now,c);
return d==-1?l:ins(l,d);
}
}
int root[N];
inline int Q(int rt,int x){return findshuz(rt,findrank(rt,x)-1);}
inline int H(int rt,int x){return findshuz(rt,findrank(rt,x+1));}
int main()
{
int n;read(n);root[0]=0;
root[0]=vnew(int(inf));
for(int i=1;i<=n;i++)
{
int opt,la,x;read(la);read(opt);read(x);
root[i]=root[la];
if(opt==1)root[i]=add(root[la],x);
else if(opt==2)root[i]=del(root[la],0,x);
else if(opt==3)printf("%d\n",findrank(root[i],x));
else if(opt==4)printf("%d\n",findshuz(root[i],x));
else if(opt==5)
{
int q=Q(root[i],x);
printf("%d\n",q<x?q:int(-inf));
}
else
{
int h=H(root[i],x);
printf("%d\n",h>x?h:int(inf));
}
}
return 0;
}
6.郁闷的出纳员
提交地址:
caioj2420: 【Leafy Tree】郁闷的出纳员
洛谷P1486 [NOI2004]郁闷的出纳员
这道题主要是实现平衡树的子树删除(对于每一次减工资,找到工资下限的位置(若没有就找到它的后继),然后删除其左边的节点即可)。
之前接触伸展树时,子树删除非常的方便——只需要找到工资下限(或者它的后继)的位置,然后将其旋转到根节点,直接cut掉它的左儿子即可。但是由于我们的
同样是找到工资下限(或者它的后继)的位置,不同的是进行搜索的过程中,如果遇到往右儿子走的情况,就直接复制右儿子的信息点到自己身上。这样就可以实现删除左儿子了。记得别忘了回收节点(大空间Leafy Tree伤不起、、)
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=200100+10;
const double A=1.0/3.0;
struct node{int l,r,c,size;}tr[2*N];
int len,sta[2*N],tp,Min,ans,root;
/*此处省略,WBLT部分同上*/
void bigclean(int x)
{
if(!x)return;
bigclean(tr[x].l);bigclean(tr[x].r);
clean(x);
}
void del(int now,int x)
{
if(tr[now].size==1)return;
maintain(now);int l=tr[now].l,r=tr[now].r;
if(x<=tr[l].c)del(l,x);
else
{
ans+=tr[l].size;bigclean(l);
tr[now]=tr[r];clean(r);del(now,x);
}
pushup(now);
}
void change(int c,int now)
{
if(!now)return;tr[now].c+=c;
change(c,tr[now].l);change(c,tr[now].r);
}
int main()
{
int n,x;scanf("%d%d",&n,&Min);ans=0;
root=vnew(0x3f3f3f3f);
while(n--)
{
char s[10];scanf("%s%d",s,&x);
if(s[0]=='I'){if(x>=Min)add(root,x);}
else if(s[0]=='A')change(x,root);
else if(s[0]=='S')
{
change(-x,root);
del(root,findshuz(root,findrank(root,Min)));
}
else
{
int siz=tr[root].size-1;
if(x>siz)printf("-1\n");
else printf("%d\n",findshuz(root,siz-x+1));
}
}
printf("%d\n",ans);
return 0;
}
7.二逼平衡树
提交地址:
洛谷P3380 【模板】二逼平衡树(树套树)
caioj2421: 【Leafy Tree】二逼平衡树
bzoj3196: Tyvj 1730 二逼平衡树
众所周知 这是一道树套树的模板题。我们观察题意,容易想到区间查找对于一棵平衡树是不现实的,所以我们用线段树套平衡树。对于线段树的每一个节点,都建一棵平衡树,然后就可以很方便地进行修改和查找了。
而对于平衡树部分,此时我们的
这是用伸展树来实现的,它不开O2过不了!(当然,可能是我的伸展树打得不够优秀、、)
而这个是用
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const double A=1.0/3.0;
const int N=50000+10,inf=2147483647;
struct segtree{int l,r,lc,rc,c;}tr[2*N];
struct node{int l,r,c,size;}Tr[100*N];
int seglen,a[N],len,sta[100*N],tp;
/*此处省略,WBLT部分同上*/
int findrank(int now,int c)
{
if(Tr[now].size==1)return Tr[now].c<c;
maintain(now);
int l=Tr[now].l,r=Tr[now].r;
if(c<=Tr[l].c)return findrank(l,c);
else return Tr[l].size+findrank(r,c);
}
inline int Q(int root,int x)
{
int R=findrank(root,x);
return !R?-inf:findshuz(root,R);
}
inline int H(int root,int x){return findshuz(root,findrank(root,x+1)+1);}
inline int blt(int l,int r)
{
int root=vnew(inf);
for(int i=l;i<=r;i++)add(root,a[i]);
return root;
}
void bt(int l,int r)
{
seglen++;int now=seglen;
tr[now].l=l;tr[now].r=r;tr[now].lc=tr[now].rc=-1;
tr[now].c=blt(l,r);
if(l<r)
{
int mid=(l+r)/2;
tr[now].lc=seglen+1;bt(l,mid);
tr[now].rc=seglen+1;bt(mid+1,r);
}
}
void change(int now,int x,int k)
{
add(tr[now].c,k);del(tr[now].c,0,a[x]);
if(tr[now].l==tr[now].r)return;
int lc=tr[now].lc,rc=tr[now].rc;
int mid=(tr[now].l+tr[now].r)/2;
if(x<=mid)change(lc,x,k);
else change(rc,x,k);
}
int Rank(int now,int l,int r,int k)
{
if(tr[now].l==l&&tr[now].r==r)return findrank(tr[now].c,k);
int lc=tr[now].lc,rc=tr[now].rc;
int mid=(tr[now].l+tr[now].r)/2;
if(r<=mid)return Rank(lc,l,r,k);
else if(mid+1<=l)return Rank(rc,l,r,k);
else return Rank(lc,l,mid,k)+Rank(rc,mid+1,r,k);
}
int shuz(int l,int r,int k)
{
int L=0,R=inf,ans=0;
while(L<=R)
{
int mid=(L+R)/2;
if(Rank(1,l,r,mid)+1<=k)L=mid+1,ans=mid;
else R=mid-1;
}
return ans;
}
int findQ(int now,int l,int r,int k)
{
if(tr[now].l==l&&tr[now].r==r)return Q(tr[now].c,k);
int lc=tr[now].lc,rc=tr[now].rc;
int mid=(tr[now].l+tr[now].r)/2;
if(r<=mid)return findQ(lc,l,r,k);
else if(mid+1<=l)return findQ(rc,l,r,k);
else return max(findQ(lc,l,mid,k),findQ(rc,mid+1,r,k));
}
int findH(int now,int l,int r,int k)
{
if(tr[now].l==l&&tr[now].r==r)return H(tr[now].c,k);
int lc=tr[now].lc,rc=tr[now].rc;
int mid=(tr[now].l+tr[now].r)/2;
if(r<=mid)return findH(lc,l,r,k);
else if(mid+1<=l)return findH(rc,l,r,k);
else return min(findH(lc,l,mid,k),findH(rc,mid+1,r,k));
}
int main()
{
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
bt(1,n);
while(m--)
{
int opt,l,r,k;scanf("%d",&opt);
if(opt==3)
{
scanf("%d%d",&l,&k);
change(1,l,k);a[l]=k;
}
else
{
scanf("%d%d%d",&l,&r,&k);
if(opt==1)printf("%d\n",Rank(1,l,r,k)+1);
else if(opt==2)printf("%d\n",shuz(l,r,k));
else if(opt==4)
{
int q=findQ(1,l,r,k);
printf("%d\n",q<k?q:-inf);
}
else
{
int h=findH(1,l,r,k);
printf("%d\n",k<h?h:inf);
}
}
}
return 0;
}
8.可持久化数组
提交地址:
洛谷P3919 【模板】可持久化数组(可持久化线段树/平衡树)
忽略二叉搜索树性质,直接用下标区分位置。然后套可持久化即可。建议先做这题再做可持久化平衡树(这题会简单一些)。
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const double A=1.0/3.0;
const int N=1000000+10;
struct node{int l,r,c,size;}tr[20*N];
int len,sta[N],tp;
/*此处省略,WBLT部分同上*/
int change(int now,int k,int c)
{
if(tr[now].size==1)return vnew(c);
maintain(now);int l=tr[now].l,r=tr[now].r;
if(k<=tr[l].size)return ins(change(l,k,c),r);
else return ins(l,change(r,k-tr[l].size,c));
}
void add(int now,int c)
{
if(tr[now].size==1)
{
tr[now].l=vnew(tr[now].c);
tr[now].r=vnew(c);pushup(now);return;
}
maintain(now);add(tr[now].r,c);pushup(now);
}
int find(int now,int k)
{
if(tr[now].size==k)return tr[now].c;
maintain(now);int l=tr[now].l,r=tr[now].r;
if(k<=tr[l].size)return find(l,k);
else return find(r,k-tr[l].size);
}
int root[N];
int main()
{
int n,m,x;scanf("%d%d%d",&n,&m,&x);
root[0]=vnew(x);for(int i=1;i<n;i++)scanf("%d",&x),add(root[0],x);
for(int i=1;i<=m;i++)
{
int v,opt,ip,c;scanf("%d%d%d",&v,&opt,&ip);
root[i]=root[v];
if(opt==1){scanf("%d",&c);root[i]=change(root[v],ip,c);}
else printf("%d\n",find(root[i],ip));
}
return 0;
}
9.报表统计
提交地址:
洛谷P1110 [ZJOI2007]报表统计
caioj2422: 【Leafy Tree】报表统计
我们维护两个堆(可以用stl的优先队列),一个储存所有元素中最接近的两个元素的差值(绝对值),一个储存相邻两个元素的之间差值(绝对值)的最小值。每次将一个数插入到合适的位置(可以用链表方便地实现),把之前的相邻差值从堆中删除,再向队中加入两个新的差值(分别为新的数和它前面的、它后面的数的差值的绝对值)。在WBLT上用和营业额统计查找方法相同的方法查找对于这个新数而言的最小差值并将它插入堆中,输出时输出堆顶即可。
对于在堆中删除任意的数,我们可以用另外一个堆,储存要删除的数。当两个堆的堆顶相同时,就同时pop即可。
code:
#pragma GCC optimize("Ofast")
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const double A=1.0/3.0;
const int N=500000+10,inf=0x3f3f3f3f;
struct node{int l,r,c,size;}tr[10*N];
int len,sta[2*N],tp,rt;
/*快读*/
/*此处省略,WBLT部分同上*/
inline int Q(int x){return findshuz(rt,findrank(rt,x)-1);}
inline int H(int x){return findshuz(rt,findrank(rt,x));}
priority_queue<int>q,d1,d2;
inline void delet(int d)
{
d2.push(d);
while(d1.size()&&d2.size()&&d1.top()==d2.top()){d1.pop();d2.pop();}
}
struct lb{int last,next,end,s;}b[2*N];int ln;
int main()
{
int n,m,x;read(n);read(m);ln=n;
rt=vnew(inf);add(rt,-inf);
for(register int i=1;i<=n;i++)
{
read(x);b[i].s=x;b[i].end=i;
if(i!=1)b[i-1].next=i,b[i].last=i-1,d1.push(-abs(b[i].s-b[i-1].s));
if(i!=n)b[i+1].last=i,b[i].next=i+1;
if(!q.size()||q.top()){int m1=Q(x),m2=H(x),z=min(abs(x-m1),abs(x-m2));q.push(-z);}
add(rt,x);
}
while(m--)
{
char s[20];scanf("%s",s);
if(s[0]=='I')
{
int x,c,e,en,t=++ln;read(x);read(c);
e=b[x].end;en=b[e].next;b[t].s=c;b[x].end=t;
if(en)b[en].last=t,b[t].next=en,d1.push(-abs(b[en].s-c));
if(e)b[e].next=t,b[t].last=e,d1.push(-abs(c-b[e].s));
if(e&&en)delet(-abs(b[en].s-b[e].s));
if(!q.size()||q.top()){int m1=Q(c),m2=H(c),z=min(abs(c-m1),abs(c-m2));q.push(-z);}
add(rt,c);
}
else
{
if(s[4]=='G')printf("%d\n",-d1.top());
else printf("%d\n",-q.top());
}
}
return 0;
}
七、尾声
(听说7是笔者最喜欢的数字,也是一个充满魔力的数字,那就到此为止吧)
对于Leafy Tree及其实现的加权平衡树的专题学习暂时就告一段落了。它是一个很优秀且平易近人的数据结构(虽然参考资料不多)。在这个过程中我也学到了很多。再次感谢所有在我学习过程中给予过我帮助的人。也希望这篇学习笔记能给你一点点帮助。