文艺平衡树笔记——fhq
文艺平衡树笔记—— \(\text{fhq Treap}\)
P3391 【模板】文艺平衡树
题意
- 给你一个数列 \(1\sim n\) ,要求支持一种操作:
- 给定一个区间 \([l,r]\) ,翻转这个区间。
- 比如, \(\text{1 2 3 4 5}\) ,翻转 \([1,3]\) 之后,得到 \(\text{3 2 1 4 5}\) 。
- 最后输出翻转后的序列。
做法
-
我们继续用 \(\text{fhq Treap}\) 做。
- \(\textit{啊啊啊这fhq怎么搞区间操作啊}\)
-
为啥不行 \((\ddot\smallfrown)\)
-
我们让 \(\text{fhq Treap}\) 向线段树学习。
- 我们给 \(\text{fhq Treap}\) 加一个线段树的
lazy_tag
,表示这棵子树要不要被翻转。 - 然后经典的标记下放。
- 下放时要异或,不是加,不是减。
- 我们给 \(\text{fhq Treap}\) 加一个线段树的
-
我们考虑分裂操作:
- 用大小分裂,可以看这篇博客
-
我们考虑合并操作
- 在合并时加上标记下放即可
-
我们考虑翻转操作
- 我们先按大小 \(l-1\) 把原树分裂成 \(x\) 和 \(z\) ,再按 \(r-l+1\) 把 \(x\) 分裂成 \(x\) 和 \(y\) 。
- 为啥是这俩数捏?
- 我们对 \([l,r]\) 操作,那么在 \(l\) 前面还有 \(l-1\) 个数,而我们要操作的数有 \(r-l+1\) 个。
- 就这样了。
-
我们考虑输出
- 就是中序遍历啦
\(\text{Code}\)
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,m;
struct node{//fhq节点定义
int l,r,key,size,val;
bool tag;
}fhq[maxn];
int cnt=0,root=0;
int newnode(int val){//新建节点
fhq[++cnt].val=val;
fhq[cnt].key=rand();
fhq[cnt].size=1;
fhq[cnt].l=fhq[cnt].r=fhq[cnt].tag=0;
return cnt;
}
void update(int x){//更新大小
fhq[x].size=fhq[fhq[x].l].size+fhq[fhq[x].r].size+1;
}
void lazy_tag(int x){//标记下放
if(!fhq[x].tag)return;
swap(fhq[x].l,fhq[x].r);
fhq[fhq[x].l].tag^=1;
fhq[fhq[x].r].tag^=1;
fhq[x].tag=0;
}
void split(int now,int size,int &x,int &y){//大小分裂
if(!now)x=y=0;
else{
lazy_tag(now);
if(fhq[fhq[now].l].size<size){
x=now;
split(fhq[now].r,size-fhq[fhq[now].l].size-1,fhq[now].r,y);
}
else{
y=now;
split(fhq[now].l,size,x,fhq[now].l);
}
update(now);
}
}
int merge(int x,int y){//合并
if(!x||!y)return x|y;
if(fhq[x].key<fhq[y].key){
lazy_tag(x);
fhq[x].r=merge(fhq[x].r,y);
update(x);
return x;
}
else{
lazy_tag(y);
fhq[y].l=merge(x,fhq[y].l);
update(y);
return y;
}
}
void rev(int l,int r){//翻转
int x,y,z;
split(root,l-1,x,y);
split(y,r-l+1,y,z);
fhq[y].tag=1;
root=merge(merge(x,y),z);
}
void print(int now){//输出
if(!now)return;
lazy_tag(now);
print(fhq[now].l);
cout<<fhq[now].val<<' ';
print(fhq[now].r);
}
int main(){//显然,主函数
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)root=merge(root,newnode(i));
for(int i=1,a,b;i<=m;i++){
scanf("%d%d",&a,&b);
rev(a,b);
}
print(root);
return 0;
}
注意逝项
- 输出过程中也要进行标记下放操作
- 标记下放很多,容易漏掉
- 大小分裂容易写错
\[\huge\textit{The End}
\]