文艺平衡树
前置知识:普通平衡树。
实际上,平衡树能做到的操作不只有插入删除,查排名查值,前驱后继这些。
如果我们把平衡树移到区间上,我们甚至能够支持比线段树更强的操作——区间翻转。
这就是文艺平衡树这道题了。
1. 区间树
首先我们来明确一下区间树的概念。
顾名思义,区间树就是用来维护区间的。
具体地,我们规定平衡树的中序遍历得到的数列就是我们所维护的数组。
比如下面这一棵树:
中序遍历得 \(1\ 3\ 5\ 4\),也就是说这棵平衡树就维护了 \(1\ 3\ 5\ 4\) 这个数组。
需要注意的是,这里我们的平衡树只是用来维护数列的一个结构;可以看到,数列中的数本身是并不符合 BST 特性的。
或者说,满足 BST 特性的是数组下标,而不是数组中的值。
2. 思路
和线段树一样,我们也需要引用区间翻转的懒标记 rev。
具体操作的时候,我们将区间对应的这棵树提取出来(不同的数据结构有不同的做法),然后打上标记。
下传标记的时候,只需要交换左右子树,把标记传到子树上即可。
下传标记的代码:
inline void pushdown(int x)
{
swap(tree[x].l,tree[x].r);//交换左右子树,是 l,r 还是 ch[0],ch[1] 依数据结构而定
tree[tree[x].ch[0]].rev^=1;//下传标记,注意这个时候如果子树上也有标记那么两个标记就抵消了
tree[tree[x].ch[1]].rev^=1;
tree[x].rev=0;
}
3. Splay 实现文艺平衡树
上文说到,打标记时需要将区间对应的树提取出来。
在 Splay 上操作是这样的:
设区间为 \([l,r]\),那么我们先将 \(l-1\) 位置的结点 Splay 到根,再将 \(r+1\) 位置的结点 Splay 到 \(l-1\) 的子节点的位置上,那么此时 \(r+1\) 的左子树就代表了 \([l,r]\) 这个区间。
实际操作的时候,我们为了保证 \(l-1\) 和 \(r+1\) 总是存在,我们会在开头和结尾插入结点表示位置 \(0\) 和 \(n+1\)。
这时我们要旋转的结点的排名就变为了 \(l\) 和 \(r+2\),这是需要注意的。
放上图以便理解:
首先我们有一棵维护区间的 Splay(结点数字表示数组下标)。
这时我们要翻转区间 \([1,3]\)。所以我们要先把 0 Splay 到根:
再把 4 Splay 到 0 的子节点的位置上:
这个时候我们发现 4 的左子树就是我们要操作的 \([1,3]\) 了。
接下来上代码:
int n,m,tot,rt;
struct node{int fa,ch[2],pos,val,cnt,siz,rev;}tree[maxn];//这个地方多了一个量 pos 来代表数组下标,方便操作
inline bool dir(int x,int f){return tree[f].ch[1]==x;}
inline void con(int x,int f,int s){tree[f].ch[s]=x;tree[x].fa=f;}
inline void pushup(int x){tree[x].siz=tree[tree[x].ch[0]].siz+tree[tree[x].ch[1]].siz+tree[x].cnt;}
inline void newnode(int &x,int f,int pos,int val){x=++tot;tree[x]=(node){f,{0,0},pos,val,1,1,0};}//当然 newnode 也要跟着改
inline void rotate(int x)
{
int f=tree[x].fa,g=tree[f].fa,k=dir(x,f);
con(tree[x].ch[k^1],f,k);con(x,g,dir(f,g));con(f,x,k^1);
pushup(f);pushup(x);
}
inline void splay(int x,int top)
{
if(!top)rt=x;
while(tree[x].fa!=top)
{
int f=tree[x].fa,g=tree[f].fa;
if(g!=top)dir(x,f)^dir(f,g)?rotate(x):rotate(f);
rotate(x);
}
}
inline void pushdown(int x)//pushdown 操作,之前说过
{
swap(tree[x].ch[0],tree[x].ch[1]);
tree[tree[x].ch[0]].rev^=1;
tree[tree[x].ch[1]].rev^=1;
tree[x].rev=0;
}
void ins(int pos,int val,int &now,int fa)
{
if(!now){newnode(now,fa,pos,val);splay(now,0);}
else if(pos<tree[now].pos)ins(pos,val,tree[now].ch[0],now);//注意因为这是棵区间树,我们要按照 pos 进行排序
else if(pos>tree[now].pos)ins(pos,val,tree[now].ch[1],now);
else{tree[now].cnt++;splay(now,0);}
}
int getnum(int rk)
{
int now=rt;
while(1)
{
if(tree[now].rev)pushdown(now);//记得 pushdown
int lsiz=tree[tree[now].ch[0]].siz;
if(rk==lsiz+1)return now;
else if(rk<=lsiz)now=tree[now].ch[0];
else{rk-=lsiz+1;now=tree[now].ch[1];}
}
}
void modify_rev(int l,int r)
{
int x=getnum(l),y,z=getnum(r+2);//这个 l 和 r+2 的意义前面已经说过了
//不要借助于 tree 的下标来确定 x 和 z,因为如果那样做中间的结点就没有 pushdown 了
splay(x,0);splay(z,x);
y=tree[tree[rt].ch[1]].ch[0];tree[y].rev^=1;
}
void tour(int x)//中序遍历输出结果
{
if(!x)return;
if(tree[x].rev)pushdown(x);//记得要 pushdown
tour(tree[x].ch[0]);
if(tree[x].pos!=0&&tree[x].pos!=n+1)printf("%d ",tree[x].val);//记得把插入的结点 0 和 n+1 去掉
tour(tree[x].ch[1]);
}
4. fhq_treap 实现文艺平衡树
有了上面 Splay 的铺垫,那么 fhq_treap 的做法就相当简单了。
fhq_treap 通过按大小分裂来取出我们需要的区间。
具体做法是,先按顺序分出一个大小为 l-1 的树,再从余下的部分按顺序分出一个大小为 r-l+1 的树,这棵树就是我们要处理的区间。
按大小分裂的写法和按权值分裂区别不大。直接看代码:
inline void split(int x,int siz,int &rx,int &ry)//按顺序分裂成两棵树 rx 和 ry,使 rx 的大小为给定的 siz
{
if(!x){rx=ry=0;return;}
if(tree[x].rev)pushdown(x);//注意了,带标记的要 pushdown
if(tree[tree[x].l].siz<siz){rx=x;split(tree[x].r,siz-tree[tree[x].l].siz-1,tree[x].r,ry);}//只不过是递归的时候要处理一下 siz 值
else{ry=x;split(tree[x].l,siz,rx,tree[x].l);}
pushup(x);
}
剩下的就没什么好说的了。注意 pushdown 就行。
inline int merge(int x,int y)
{
if(!x||!y)return x+y;
if(tree[x].key<tree[y].key)
{
if(tree[x].rev)pushdown(x);//要 pushdown
tree[x].r=merge(tree[x].r,y);pushup(x);return x;
}
else
{
if(tree[y].rev)pushdown(y);
tree[y].l=merge(x,tree[y].l);pushup(y);return y;
}
}
void ins(int pos,int val)//为了方便按大小分裂,插入时多给一个参数 pos
{
int now,x,y;
split(rt,pos,x,y);newnode(now,val);
rt=merge(merge(x,now),y);
}
void modify_rev(int l,int r)
{
int x,y,z;
split(rt,l-1,x,y);split(y,r-l+1,y,z);//前面说的两次分裂
tree[y].rev^=1;
rt=merge(merge(x,y),z);
}
void tour(int x)//中序遍历输出结果,别忘了 pushdown
{
if(!x)return;
if(tree[x].rev)pushdown(x);
tour(tree[x].l);
printf("%d ",tree[x].val);
tour(tree[x].r);
}