Loading

文艺平衡树笔记——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 ,表示这棵子树要不要被翻转。
    • 然后经典的标记下放。
    • 下放时要异或,不是加,不是减。
  • 我们考虑分裂操作:

  • 我们考虑合并操作

    • 在合并时加上标记下放即可
  • 我们考虑翻转操作

    • 我们先按大小 \(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;
}

注意逝项

  1. 输出过程中也要进行标记下放操作
  2. 标记下放很多,容易漏掉
  3. 大小分裂容易写错

\[\huge\textit{The End} \]

posted @ 2023-10-19 06:55  洛谷Augury  阅读(87)  评论(0编辑  收藏  举报