一种神奇的平衡树——WBLT
参考文献
王思齐《Leafy Tree 及其实现的加权平衡树》
WBLT 是一种神奇的平衡树,全名 Weight Balanced Leafy Tree。
lxl 在冬令营上简单地提及了这个东西,这个东西非常好用,当然的确非常好用。
下面我们来介绍这个东西。
Leafy Tree
Leafy Tree,顾名思义,是一类只有叶子结点存储信息的数据结构,一个常见的例子是线段树。
用 Leafy Tree 维护 BST,其实跟线段树的写法是类似的。先抛开平衡,以 普通平衡树 为例,我们逐一介绍维护方法。
- 基本性质
先了解一下基本的性质。
每个点要么有两个儿子,要么没有儿子,显然对于 Leafy Tree 来说只有一个儿子的点没什么用。
每个点(包括非叶子)需要存储一个权值 \(val\),如果是叶子存的就是键值,否则存的是子树内叶子的权值最大值。
由于子树内叶子的权值是中序遍历单调不降的,一个非叶点的权值是右儿子的权值。
每个点也可以同时维护子树内的叶子大小,这和其他平衡树是类似的。
下面一部分代码中的 maintain
是维护平衡,读者可以先忽略。
- 插入一个元素
ins
从根开始,逐一比较插入的值和左子树的权值,这和线段树上二分是类似的。
当我们到达叶子时,我们需要将插入的值和叶子合并。
一个显然的想法是,新建两个点,一个是表示插入的值的叶子,一个是这两个叶子的父节点。
下图是一个例子。
点击查看代码
void ins(ll p,ll v){
if(isleaf(p)){
ls(p)=Newnode(min(v,a[p].val));
rs(p)=Newnode(max(v,a[p].val));
pushup(p); return;
}
if(v<=a[ls(p)].val) ins(ls(p),v);
else ins(rs(p),v);
pushup(p); maintain(p);
}
- 删除一个元素
del
依然从根开始,走到对应的叶子,此时我们需要删掉这个叶子。
但是直接删会破坏性质(每个点要么两个儿子,要么没有儿子),需要把该叶子的兄弟搬到父亲上来。
只需要在递归的同时记录一下父亲即可。
点击查看代码
void del(ll p,ll v,ll fa){
if(isleaf(p)){
if(ls(fa)==p) a[fa]=a[rs(fa)];
else a[fa]=a[ls(fa)];
return;
}
if(v<=a[ls(p)].val) del(ls(p),v,p);
else del(rs(p),v,p);
if(!isleaf(p)) pushup(p);
maintain(p);
}
维护平衡
显然 Leafy Tree 可以被卡成平方。
因此引入 Weight Balanced Leafy Tree,来保持平衡。
具体的,WBLT 和许多平衡树一样,依靠旋转来保持平衡。
我们定义平衡因子 \(\alpha\),表示定义对于一个非叶点 \(p\),当且仅当 \(\min(siz[ls(p)],siz[rs(p)])\ge \alpha \cdot siz[p]\) 时点 \(p\) 平衡。
为了维护平衡,WBLT 使用了单旋和双旋两种方式。
- 单旋
对于一个不平衡的点 \(p\),不妨设 \(siz[ls(p)]>siz[rs(p)]\),此时我们把 \(ls(p)\) 旋转上来。
不难发现 \(ls(p)\) 一定不是叶子,那么 \(ls(p)\) 本质上是不存储信息的,为了不改变指针,我们可以直接把 \(ls(p)\) 旋到 \(p\) 的右儿子。
具体的:
但是旋转之后真的能保持平衡吗?考虑计算 \(p\) 和旋转后 \(rs(p)\) 能平衡的条件。
太麻烦了,略。
- 双旋
单旋对于 WBLT 来说是错误的,正确的解法是双旋。
具体的,假设我们要旋转 \(ls(p)\),我们需要先判断 \(ls(p)\) 的右边是否过重,否则旋转之后可能仍然不平衡。
具体的做法是,判断 \(siz[rs(ls(p))]>\frac{1-2\alpha}{1-\alpha} siz[ls(p)]\),若满足条件则把 \(ls(p)\) 的右儿子旋转。
证明见 oi-wiki
。(直接背就行
一般取 \(\alpha =0.25\)。
点击查看代码
void rot(ll p,ll d){
if(d==0){
ll tmp=ls(p);
ls(p)=ls(ls(p));
ls(tmp)=rs(tmp);
rs(tmp)=rs(p);
rs(p)=tmp; pushup(tmp);
} else{
ll tmp=rs(p);
rs(p)=rs(rs(p));
rs(tmp)=ls(tmp);
ls(tmp)=ls(p);
ls(p)=tmp; pushup(tmp);
} pushup(p);
}
void maintain(ll p){
if(a[ls(p)].siz>a[rs(p)].siz*3){
if(a[rs(ls(p))].siz>a[ls(ls(p))].siz*2){
rot(ls(p),1);
} rot(p,0);
} else if(a[rs(p)].siz>a[ls(p)].siz*3){
if(a[ls(rs(p))].siz>a[rs(rs(p))].siz*2){
rot(rs(p),0);
} rot(p,1);
}
}
然后其他操作就直接略了。
下面是普通平衡树的代码。
点击查看代码
#include<bits/stdc++.h>
#define ls(p) a[p].lc
#define rs(p) a[p].rc
#define ll long long
using namespace std;
const ll maxn=2e5+10;
ll n,op,x,tot,rt;
struct node{
ll lc,rc,val,siz;
}a[maxn];
ll Newnode(ll v){
++tot;
a[tot].val=v, a[tot].siz=1;
return tot;
}
bool isleaf(ll p){
return !ls(p)||!rs(p);
}
void pushup(ll p){
a[p].siz=a[ls(p)].siz+a[rs(p)].siz;
a[p].val=a[rs(p)].val;
}
void rot(ll p,ll d){
if(d==0){
ll tmp=ls(p);
ls(p)=ls(ls(p));
ls(tmp)=rs(tmp);
rs(tmp)=rs(p);
rs(p)=tmp; pushup(tmp);
} else{
ll tmp=rs(p);
rs(p)=rs(rs(p));
rs(tmp)=ls(tmp);
ls(tmp)=ls(p);
ls(p)=tmp; pushup(tmp);
} pushup(p);
}
void maintain(ll p){
if(a[ls(p)].siz>a[rs(p)].siz*3){
if(a[rs(ls(p))].siz>a[ls(ls(p))].siz*2){
rot(ls(p),1);
} rot(p,0);
} else if(a[rs(p)].siz>a[ls(p)].siz*3){
if(a[ls(rs(p))].siz>a[rs(rs(p))].siz*2){
rot(rs(p),0);
} rot(p,1);
}
}
void ins(ll p,ll v){
if(isleaf(p)){
ls(p)=Newnode(min(v,a[p].val));
rs(p)=Newnode(max(v,a[p].val));
pushup(p); return;
}
if(v<=a[ls(p)].val) ins(ls(p),v);
else ins(rs(p),v);
pushup(p); maintain(p);
}
void del(ll p,ll v,ll fa){
if(isleaf(p)){
if(ls(fa)==p) a[fa]=a[rs(fa)];
else a[fa]=a[ls(fa)];
return;
}
if(v<=a[ls(p)].val) del(ls(p),v,p);
else del(rs(p),v,p);
if(!isleaf(p)) pushup(p);
maintain(p);
}
ll Count(ll p,ll v){
if(isleaf(p)) return a[p].val<=v;
if(a[ls(p)].val>v) return Count(ls(p),v);
return Count(rs(p),v)+a[ls(p)].siz;
}
ll find(ll p,ll k){
if(a[p].siz==k) return a[p].val;
if(a[ls(p)].siz>=k) return find(ls(p),k);
else return find(rs(p),k-a[ls(p)].siz);
}
int main(){
rt=Newnode(1e17);
scanf("%lld",&n);
while(n--){
scanf("%lld%lld",&op,&x);
if(op==1) ins(rt,x);
else if(op==2) del(rt,x,0);
else if(op==3) printf("%lld\n",Count(rt,x-1)+1);
else if(op==4) printf("%lld\n",find(rt,x));
else if(op==5) printf("%lld\n",find(rt,Count(rt,x-1)));
else printf("%lld\n",find(rt,Count(rt,x)+1));
}
return 0;
}
和 splay 对比一下。
测试点编号 | Splay | WBLT |
---|---|---|
1 | \(3ms\) | \(3ms\) |
2 | \(3ms\) | \(3ms\) |
3 | \(3ms\) | \(3ms\) |
4 | \(3ms\) | \(3ms\) |
5 | \(4ms\) | \(3ms\) |
6 | \(10ms\) | \(3ms\) |
7 | \(45ms\) | \(4ms\) |
8 | \(3ms\) | \(6ms\) |
9 | \(94ms\) | \(21ms\) |
10 | \(94ms\) | \(44ms\) |
不难发现 WBLT 还挺快。
更常规的操作
- 合并
合并两棵 WBLT,不妨设根为 \(p,q\)。
钦定 \(siz[p]\ge siz[q]\)
根据论文,进行分类讨论:
若 \(siz[q]\ge \alpha (siz[p]+siz[q])\),直接新建一个点,为 \(p,q\) 的父节点即可。
否则,若 \(siz[ls(p)]\ge \alpha (siz[p]+siz[q])\),递归合并 \(rs(p)\) 和 \(q\),然后和 \(ls(p)\) 合并。
否则,分别递归合并 \(ls(p),ls(rs(p))\),以及 \(rs(rs(p)),q\),最后把两个合并。
点击查看代码
ll merge(ll p,ll q){
if(!p||!q) return p|q;
if(4*min(a[p].siz,a[q].siz)>=(a[p].siz+a[q].siz)){
ll x=Newnode(0);
ls(x)=p, rs(x)=q;
pushup(x); return x;
}
if(a[p].siz>=a[q].siz){
if(4*a[ls(p)].siz>=a[p].siz+a[q].siz) return merge(ls(p),merge(rs(p),q));
else return merge(merge(ls(p),ls(rs(p))),merge(rs(rs(p)),q));
} else{
if(4*a[rs(q)].siz>=a[p].siz+a[q].siz) return merge(merge(p,ls(q)),rs(q));
else return merge(merge(p,ls(ls(q))),merge(rs(ls(q)),rs(q)));
}
}
- 分裂
类似于线段树,先走到叶子。
然后逐一将分裂的一边的所有子树合并。
分裂的时间复杂度都是 \(O(\log n)\) 的,而合并是 \(O(\log \frac{maxsize}{minsize})\)
但是常数有一点大,注意分裂后走过的路径上的点都会废掉,一定要垃圾回收,否则空间容易爆炸。
点击查看代码
void split(ll p,ll k,ll &x,ll &y){
if(isleaf(p)){
if(k==0) y=p, x=0;
else x=p, y=0;
return;
}
if(a[ls(p)].siz>=k){
split(ls(p),k,x,y);
y=merge(y,rs(p));
} else{
split(rs(p),k-a[ls(p)].siz,x,y);
x=merge(ls(p),x);
} trs[++len]=p;
}
有了分裂与合并,WBLT 也能支持区间翻转。
点击查看代码
#include<bits/stdc++.h>
#define ls(p) a[p].lc
#define rs(p) a[p].rc
#define ll int
using namespace std;
const ll maxn=1e7+10;
ll n,m,l,r,tot,rt,trs[maxn],len;
struct node{
ll lc,rc,val,siz,rev;
}a[maxn];
ll Newnode(ll v){
ll p=(len? trs[len--]:++tot);
a[p].val=v, a[p].siz=1;
ls(p)=rs(p)=a[p].rev=0;
return p;
}
bool isleaf(ll p){
return !ls(p)||!rs(p);
}
void pushup(ll p){
a[p].siz=a[ls(p)].siz+a[rs(p)].siz;
a[p].val=a[rs(p)].val;
}
void pushdown(ll p){
if(!a[p].rev) return;
a[p].rev=0;
swap(ls(p),rs(p));
a[ls(p)].rev^=1, a[rs(p)].rev^=1;
}
void rot(ll p,ll d){
if(d==0){
ll tmp=ls(p);
ls(p)=ls(ls(p));
ls(tmp)=rs(tmp);
rs(tmp)=rs(p);
rs(p)=tmp; pushup(tmp);
} else{
ll tmp=rs(p);
rs(p)=rs(rs(p));
rs(tmp)=ls(tmp);
ls(tmp)=ls(p);
ls(p)=tmp; pushup(tmp);
} pushup(p);
}
void maintain(ll p){
if(a[ls(p)].siz>a[rs(p)].siz*3){
if(a[rs(ls(p))].siz>a[ls(ls(p))].siz*2){
rot(ls(p),1);
} rot(p,0);
} else if(a[rs(p)].siz>a[ls(p)].siz*3){
if(a[ls(rs(p))].siz>a[rs(rs(p))].siz*2){
rot(rs(p),0);
} rot(p,1);
}
}
ll build(ll l,ll r){
if(l==r){
return Newnode(l);
} ll mid=l+r>>1;
ll p=++tot;
ls(p)=build(l,mid), rs(p)=build(mid+1,r);
pushup(p); return p;
}
ll merge(ll p,ll q){
if(!p||!q) return p|q;
if(4*min(a[p].siz,a[q].siz)>=(a[p].siz+a[q].siz)){
ll x=Newnode(0);
ls(x)=p, rs(x)=q;
pushup(x); return x;
}
if(a[p].siz>=a[q].siz){
pushdown(p);
if(4*a[ls(p)].siz>=a[p].siz+a[q].siz) return merge(ls(p),merge(rs(p),q));
else{
pushdown(rs(p));
return merge(merge(ls(p),ls(rs(p))),merge(rs(rs(p)),q));
}
} else{
pushdown(q);
if(4*a[rs(q)].siz>=a[p].siz+a[q].siz) return merge(merge(p,ls(q)),rs(q));
else{
pushdown(ls(q));
return merge(merge(p,ls(ls(q))),merge(rs(ls(q)),rs(q)));
}
}
}
void split(ll p,ll k,ll &x,ll &y){
if(isleaf(p)){
if(k==0) y=p, x=0;
else x=p, y=0;
return;
}
pushdown(p);
if(a[ls(p)].siz>=k){
split(ls(p),k,x,y);
y=merge(y,rs(p));
} else{
split(rs(p),k-a[ls(p)].siz,x,y);
x=merge(ls(p),x);
} trs[++len]=p;
}
void dfs(ll p){
if(isleaf(p)){
printf("%d ",a[p].val); return;
}
pushdown(p);
dfs(ls(p));
dfs(rs(p));
}
ll s1,s2,s3;
int main(){
scanf("%d%d",&n,&m);
rt=build(1,n);
for(ll i=1;i<=m;i++){
scanf("%d%d",&l,&r);
split(rt,r,s1,s3);
split(s1,l-1,s1,s2);
a[s2].rev^=1;
rt=merge(merge(s1,s2),s3);
}
dfs(rt);
return 0;
}
与 Splay 和 fhq-treap 的比较(根据 文艺平衡树)
测试点 | Splay | fhq-treap | WBLT |
---|---|---|---|
1 | \(4ms\) | \(4ms\) | \(3ms\) |
2 | \(4ms\) | \(4ms\) | \(4ms\) |
3 | \(13ms\) | \(14ms\) | \(17ms\) |
4 | \(147ms\) | \(188ms\) | \(207ms\) |
5 | \(151ms\) | \(185ms\) | \(202ms\) |
6 | \(102ms\) | \(91ms\) | \(156ms\) |
结果 WBLT 成了最慢的,惊奇的是 Splay 反倒成了最快的。
可知在只有区间翻转的时候,Splay 比较占优势。
或者说,分裂和合并的常数比较大?而且 Splay 不需要分裂和合并。
持久化操作
当然 WBLT 也支持持久化。
在插入和删除时,和其他数据结构类似,WBLT 也是采用路径复制的方法。
在旋转 \(ls(p)\) 的时候,不难发现只有插入和删除会触发旋转,因此 \(p\) 是在插入或删除的时候已经通过路径复制新建的点,我们不需要对 \(p\) 额外复制。
但是,\(ls(p)\) 可能不是路径复制上的点,我们需要对 \(ls(p)\) 复制。
点击查看代码
#include<bits/stdc++.h>
#define ls(p) a[p].lc
#define rs(p) a[p].rc
#define ll long long
using namespace std;
const ll maxn=1e7+10;
ll n,t,op,x,tot,rt[maxn],c;
struct node{
ll lc,rc,val,siz;
}a[maxn];
ll Newnode(ll v){
++tot;
a[tot].val=v, a[tot].siz=1;
return tot;
}
bool isleaf(ll p){
return !ls(p)||!rs(p);
}
void pushup(ll p){
a[p].siz=a[ls(p)].siz+a[rs(p)].siz;
a[p].val=a[rs(p)].val;
}
void rot(ll p,ll d){
if(d==0){
a[++tot]=a[ls(p)], ls(p)=tot;
ll tmp=ls(p);
ls(p)=ls(ls(p));
ls(tmp)=rs(tmp);
rs(tmp)=rs(p);
rs(p)=tmp; pushup(tmp);
} else{
a[++tot]=a[rs(p)], rs(p)=tot;
ll tmp=rs(p);
rs(p)=rs(rs(p));
rs(tmp)=ls(tmp);
ls(tmp)=ls(p);
ls(p)=tmp; pushup(tmp);
} pushup(p);
}
void maintain(ll p){
if(a[ls(p)].siz>a[rs(p)].siz*3){
if(a[rs(ls(p))].siz>a[ls(ls(p))].siz*2){
rot(ls(p),1);
}
rot(p,0);
} else if(a[rs(p)].siz>a[ls(p)].siz*3){
if(a[ls(rs(p))].siz>a[rs(rs(p))].siz*2){
rot(rs(p),0);
}
rot(p,1);
}
}
void ins(ll &p,ll v){
a[++tot]=a[p]; p=tot;
if(isleaf(p)){
ls(p)=Newnode(min(v,a[p].val));
rs(p)=Newnode(max(v,a[p].val));
pushup(p); return;
}
if(v<=a[ls(p)].val) ins(ls(p),v);
else ins(rs(p),v);
pushup(p); maintain(p);
}
void del(ll &p,ll v,ll fa){
if(isleaf(p)){
if(a[p].val!=v) return;
if(ls(fa)==p) a[fa]=a[rs(fa)];
else a[fa]=a[ls(fa)];
return;
}
a[++tot]=a[p]; p=tot;
if(v<=a[ls(p)].val) del(ls(p),v,p);
else del(rs(p),v,p);
if(!isleaf(p)) pushup(p);
maintain(p);
}
ll Count(ll p,ll v){
if(isleaf(p)) return a[p].val<=v;
if(a[ls(p)].val>v) return Count(ls(p),v);
return Count(rs(p),v)+a[ls(p)].siz;
}
ll find(ll p,ll k){
if(a[p].siz==k) return a[p].val;
if(a[ls(p)].siz>=k) return find(ls(p),k);
else return find(rs(p),k-a[ls(p)].siz);
}
int main(){
rt[0]=tot=1;
ls(rt[0])=Newnode(-0x7fffffff), rs(rt[0])=Newnode(0x7fffffff);
pushup(rt[0]);
scanf("%lld",&n);
for(ll i=1;i<=n;i++){
scanf("%lld%lld%lld",&t,&op,&x);
rt[i]=rt[t];
if(op==1) ins(rt[i],x), ++c;
else if(op==2) del(rt[i],x,0), --c;
else if(op==3) printf("%lld\n",Count(rt[i],x-1));
else if(op==4) printf("%lld\n",find(rt[i],x+1));
else if(op==5) printf("%lld\n",find(rt[i],Count(rt[i],x-1)));
else printf("%lld\n",find(rt[i],Count(rt[i],x)+1));
}
return 0;
}
- 分裂与合并的持久化
考虑合并的持久化。
观察合并的代码,不难发现合并的过程中并没有破坏原来树的结构,实际上普通的合并已经满足可持久化了。
分裂同理,也满足。
模板题中同时存在插入和翻转操作,注意叶子标记的处理。
点击查看代码
#include<bits/stdc++.h>
#define ls(p) a[p].lc
#define rs(p) a[p].rc
#define ll long long
using namespace std;
const ll maxn=2e7+10;
ll n,t,op,x,y,tot,c,s1,s2,s3;
int rt[200010];
struct node{
int siz,lc,rc,rev;
ll sum;
}a[maxn];
ll Newnode(ll v){
++tot;
a[tot].siz=1, a[tot].sum=v;
return tot;
}
bool isleaf(ll p){
return !ls(p)||!rs(p);
}
void pushup(ll p){
a[p].sum=a[ls(p)].sum+a[rs(p)].sum;
a[p].siz=a[ls(p)].siz+a[rs(p)].siz;
}
void pushdown(ll p){
if(isleaf(p)||!a[p].rev) return;
a[p].rev=0;
if(!isleaf(ls(p))) a[++tot]=a[ls(p)], ls(p)=tot;
if(!isleaf(rs(p))) a[++tot]=a[rs(p)], rs(p)=tot;
a[ls(p)].rev^=1, a[rs(p)].rev^=1;
swap(ls(p),rs(p));
}
void rot(ll p,ll d){
if(d==0){
ll tmp=++tot; a[tmp]=a[ls(p)];
ls(p)=ls(tmp);
ls(tmp)=rs(tmp), rs(tmp)=rs(p);
rs(p)=tmp; pushup(tmp);
} else{
ll tmp=++tot; a[tmp]=a[rs(p)];
rs(p)=rs(tmp);
rs(tmp)=ls(tmp), ls(tmp)=ls(p);
ls(p)=tmp; pushup(tmp);
} pushup(p);
}
void maintain(ll p){
pushdown(p);
if(a[rs(p)].siz*3<a[ls(p)].siz){
pushdown(ls(p));
if(a[ls(ls(p))].siz*2<a[rs(ls(p))].siz){
pushdown(rs(ls(p))); rot(ls(p),1);
} rot(p,0);
} else if(a[ls(p)].siz*3<a[rs(p)].siz){
pushdown(rs(p));
if(a[rs(rs(p))].siz*2<a[ls(rs(p))].siz){
pushdown(ls(rs(p))); rot(rs(p),0);
} rot(p,1);
}
}
void ins(int &p,ll k,ll v){
a[++tot]=a[p], p=tot;
if(isleaf(p)){
ls(p)=Newnode(k==0? v:a[p].sum), rs(p)=Newnode(k==1? v:a[p].sum);
a[p].rev=0; //注意叶子标记问题!!!
pushup(p); return;
} pushdown(p);
if(k<=a[ls(p)].siz) ins(ls(p),k,v);
else ins(rs(p),k-a[ls(p)].siz,v);
pushup(p); maintain(p);
}
void del(int &p,ll k){
a[++tot]=a[p], p=tot;
pushdown(p);
if(k<=a[ls(p)].siz){
if(isleaf(ls(p))) a[p]=a[rs(p)];
else{
del(ls(p),k);
pushup(p); maintain(p);
}
} else{
if(isleaf(rs(p))) a[p]=a[ls(p)];
else {
del(rs(p),k-a[ls(p)].siz);
pushup(p); maintain(p);
}
}
}
ll merge(ll p,ll q){
if(!p||!q) return p|q;
if(4*min(a[p].siz,a[q].siz)>=a[p].siz+a[q].siz){
ll r=++tot; ls(r)=p, rs(r)=q;
pushup(r); return r;
}
if(a[p].siz>=a[q].siz){
pushdown(p);
if(4*a[ls(p)].siz>=a[p].siz+a[q].siz) return merge(ls(p),merge(rs(p),q));
pushdown(rs(p));
return merge(merge(ls(p),ls(rs(p))),merge(rs(rs(p)),q));
} else{
pushdown(q);
if(4*a[rs(q)].siz>=a[p].siz+a[q].siz) return merge(merge(p,ls(q)),rs(q));
pushdown(ls(q));
return merge(merge(p,ls(ls(q))),merge(rs(ls(q)),rs(q)));
}
}
void split(ll p,ll k,ll &x,ll &y){
if(isleaf(p)){
if(k==1) x=p, y=0;
else x=0, y=p;
return;
} pushdown(p);
if(a[ls(p)].siz>=k){
split(ls(p),k,x,y);
y=merge(y,rs(p));
} else{
split(rs(p),k-a[ls(p)].siz,x,y);
x=merge(ls(p),x);
}
}
int main(){
rt[0]=Newnode(0);
scanf("%lld",&n);
for(ll i=1,lst=0;i<=n;i++){
scanf("%lld%lld%lld",&t,&op,&x); x^=lst;
rt[i]=rt[t];
if(op==1){
scanf("%lld",&y); y^=lst;
ins(rt[i],x,y);
} else if(op==2){
del(rt[i],x);
} else if(op==3){
scanf("%lld",&y); y^=lst;
split(rt[i],y,s1,s3);
split(s1,x-1,s1,s2);
a[s2].rev^=1;
rt[i]=merge(merge(s1,s2),s3);
} else{
scanf("%lld",&y); y^=lst;
split(rt[i],y,s1,s3);
split(s1,x-1,s1,s2);
printf("%lld\n",lst=a[s2].sum);
}
}
return 0;
}
此处 fhq-treap 与 WBLT 的比较
测试点编号 | WBLT | fhq-treap |
---|---|---|
1 | \(9ms\) | \(9ms\) |
2 | \(8ms\) | \(9ms\) |
3 | \(9ms\) | \(9ms\) |
4 | \(8ms\) | \(9ms\) |
5 | \(9ms\) | \(9ms\) |
6 | \(9ms\) | \(9ms\) |
7 | \(303ms\) | \(378ms\) |
8 | \(294ms\) | \(380ms\) |
9 | \(295ms\) | \(379ms\) |
10 | \(298ms\) | \(388ms\) |
11 | \(293ms\) | \(383ms\) |
12 | \(315ms\) | \(372ms\) |
13 | \(292ms\) | \(356ms\) |
14 | \(286ms\) | \(351ms\) |
15 | \(281ms\) | \(352ms\) |
16 | \(282ms\) | \(352ms\) |
17 | \(289ms\) | \(347ms\) |
18 | \(288ms\) | \(340ms\) |
19 | \(297ms\) | \(338ms\) |
20 | \(294ms\) | \(339ms\) |
21 | \(278ms\) | \(280ms\) |
不难发现 WBLT 还是比较快的。
- 一个小优化
在 pushdown 下放标记时,设当前点为 \(p\),若 \(p\) 在持久化 WBLT 上是 \(ls(p)\) 的唯一父亲,我们便并不需要新建结点。
具体的,对于每个点记录一个 use,表示该点父亲数量。若 use \(\le 1\) 则不需要新建点,否则新建一个点,新的 use 赋为 \(1\),原来点的 use 减一。
在复制一个点时,令两个儿子的 use 加一。
总结
WBLT 还算是比较优秀的平衡树,时间复杂度一般优于 (fhq-)Treap 和 Splay,可能和替罪羊差不多吧。但是 WBLT 支持完全持久化,这就超越了许多的平衡树。
简单来说,除了 LCT,处处可用 WBLT。
一些例题
CF702F T-Shirts
题意:一个序列 \(a_{1...n}\),多次询问,每次给出一个数 \(k\),从 \(1...n\) 扫描每个 \(a_i\),若 \(k\ge a_i\) 则 \(k\gets k-a_i\),问一共减了多少次。\(1\le n\le 2\times 5\)
考虑 dp。设 \(f[i,j]\) 表示从 \(i\) 开始扫,当前数字为 \(j\),会减多少次。
若 \(j<a_i\),则 \(f[i,j]=f[i+1,j]\);若 \(j\ge a_i\),则 \(f[i+1,j-a_i]+1\)。
相当于把 \(f[i+1,0...a_i-1]\) 这部分原封不动复制过来,然后 \(f[i+1,]\) 整个数组往右移 \(a_i\) 位并 \(+1\) 放到 \(f[i,a_i...]\)。
可以用持久化 WBLT 实现数组 \(+1\) 和数组合并。
但是至今仍然过不了,毕竟这玩意空间是问题。
经过几个小时的大战,我终于过了。
只需要合并随机化即可。
点击查看代码
#include<bits/stdc++.h>
#define ls(p) a[p].lc
#define rs(p) a[p].rc
#define fi first
#define se second
#define mkp make_pair
#define ll int
#define pir pair<ll,ll>
#define pb push_back
#define ls(p) a[p].lc
#define rs(p) a[p].rc
using namespace std;
const ll maxn=2e5+10, M=53e6+10, inf=1e9+3;
const double alpha=0.25;
struct WBLT{
ll rt[maxn],tot;
struct node{
ll lc,rc,tag,use;
ll siz;
}a[M];
void pushup(ll p){
a[p].siz=a[ls(p)].siz+a[rs(p)].siz;
if(a[p].siz>inf) a[p].siz=inf;
}
bool isleaf(ll p){
return !ls(p)||!rs(p);
}
void refresh(ll &p){
if(a[p].use<=1) return;
--a[p].use;
a[++tot]=a[p]; p=tot;
++a[p].use;
if(!isleaf(p)) ++a[ls(p)].use, ++a[rs(p)].use;
}
void pushdown(ll p){
if(isleaf(p)||!a[p].tag) return;
refresh(ls(p));
refresh(rs(p));
a[ls(p)].tag+=a[p].tag, a[rs(p)].tag+=a[p].tag;
a[p].tag=0;
}
ll merge(ll p,ll q){
if(!p||!q) return p|q;
if(min(a[p].siz,a[q].siz)>=alpha*(a[p].siz+a[q].siz)||rand()<10000){
ll x=++tot;
ls(x)=p, rs(x)=q;
++a[p].use, ++a[q].use;
pushup(x); return x;
}
if(a[p].siz>=a[q].siz){
pushdown(p);
if(a[ls(p)].siz>=alpha*(a[p].siz+a[q].siz)||rand()<10000) return merge(ls(p),merge(rs(p),q));
else{
pushdown(rs(p));
return merge(merge(ls(p),ls(rs(p))),merge(rs(rs(p)),q));
}
} else{
pushdown(q);
if(a[rs(q)].siz>=alpha*(a[p].siz+a[q].siz)||rand()<10000) return merge(merge(p,ls(q)),rs(q));
else{
pushdown(ls(q));
return merge(merge(p,ls(ls(q))),merge(rs(ls(q)),rs(q)));
}
}
}
void split(ll p,long long k,ll &x,ll &y){
if(isleaf(p)){
if(k==0) y=p, x=0;
else x=p, y=0;
return;
}
pushdown(p);
if(a[ls(p)].siz>=k){
split(ls(p),k,x,y);
y=merge(y,rs(p));
} else{
split(rs(p),k-a[ls(p)].siz,x,y);
x=merge(ls(p),x);
}
}
ll query(ll p,ll k){
if(isleaf(p)) return a[p].tag;
pushdown(p);
if(a[ls(p)].siz>=k) return query(ls(p),k);
return query(rs(p),k-a[ls(p)].siz);
}
}T;
struct Ts{
ll c,q;
}b[maxn];
bool cmp(Ts a,Ts b){
return a.q==b.q? a.c<b.c:a.q>b.q;
}
ll n,m,w[maxn];
int main(){
scanf("%d",&n); srand(time(0));
for(ll i=1;i<=n;i++){
scanf("%d%d",&b[i].c,&b[i].q);
}
sort(b+1,b+1+n,cmp);
T.rt[n+1]=T.tot=1;
T.a[1].siz=1;
for(ll i=1;i<=30;i++) T.rt[n+1]=T.merge(T.rt[n+1],T.rt[n+1]);
for(ll i=n;i;i--){
ll s1=0, s2=0;
T.split(T.rt[i+1],b[i].c,s1,s2);
T.a[++T.tot]=T.a[T.rt[i+1]];
++T.a[T.tot].tag;
T.rt[i]=T.merge(s1,T.tot);
}
scanf("%d",&m);
for(ll i=1;i<=m;i++){
ll x; scanf("%d",&x);
printf("%d ",T.query(T.rt[1],x+1));
}
return 0;
}