【学习笔记】fhq Treap实现文艺平衡树

没有学习过 fhq Treap 的可以看我上一篇文章,看过的建议去再看看分裂和合并操作

回顾

在上一篇文章中提到,fhq Treap 可以支持比较多的操作,文艺平衡树就是其中一种,其实就是可以实现区间操作(翻转)的平衡树

文艺平衡树

板子在这里

fhq Treap 实现文艺平衡树的步骤是:

  1. 将树按大小分裂成小于 l l l 和 大于等于 l l l 的两颗树
  2. 把大的那颗树再按 r − l + 1 r-l+1 rl+1 分裂掉
  3. 把得到的大小为 r − l + 1 r-l+1 rl+1 的树打上标记

这里用到了线段树懒标记的思想,没有学过线段树的看这里

不懂为什么按大小分裂可以的先别着急,接着往下看

push_up

和前面讲的是一样的,就不赘述了

void push_up(int pos){
	fhq[pos].siz = fhq[fhq[pos].l].siz + fhq[fhq[pos].r].siz + 1;
}

push_down

和线段树一样的,如果当前位置存在翻转,那就翻转掉,然后把标记下传到左右子树去,记得清空懒标记

这里使用异或,也就是原来翻转的现在不翻转了,原来不翻转的现在翻转了

void push_down(int pos){
	if(fhq[pos].tag){
		swap(fhq[pos].l,fhq[pos].r);
		fhq[fhq[pos].l].tag ^= 1;
		fhq[fhq[pos].r].tag ^= 1;
		fhq[pos].tag = 0;
	}
}

关于 push_down 放在哪里,在要修改当前节点的时候就下传,如果你分不清,还有一种方法是哪哪都放一个

分裂

这里是按照大小分裂,其实可以理解为按照 rank 分裂,因为如果画张图会发现,一开始的树的中序遍历其实就是原序列,所以按照大小其实就是按照原来的 rank 去把区间取出来再进行操作

个人感觉可以参考上一篇 查询排名为 rank 的值 这个函数来理解

void split(int pos, int siz , int &x, int &y){
	if(!pos){
		x = y = 0;
		return;
	}
	push_down(pos);
	if(fhq[fhq[pos].l].siz < siz){
		x = pos;
		split(fhq[pos].r,siz - fhq[fhq[pos].l].siz-1,fhq[pos].r,y);
	}else{
		y = pos;
		split(fhq[pos].l,siz,x,fhq[pos].l);
	}
	push_up(pos);
}

可能把 s i z siz siz 改成 r a n k rank rank 会更好理解一点?

合并

合并其实也就是多了个 push_down 其他没啥区别

int merge(int x, int y){
	if(!x || !y) return x+y;
	if(fhq[x].key < fhq[y].key){
		push_down(x);
		fhq[x].r = merge(fhq[x].r,y);
		push_up(x);
		return x;
	}else{
		push_down(y);
		fhq[y].l = merge(x,fhq[y].l);
		push_up(y);
		return y; 
	}
}

翻转

翻转直接按照上面说的步骤来就可以了

void reverse(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 pos){
	if(!pos) return;
	push_down(pos);
	print(fhq[pos].l);
	cout << fhq[pos].val << " ";
	print(fhq[pos].r);
}

Code

这里构造原序列的时候,因为有一个性质:序列是顺序的,所以后加入的节点一定比前面的节点都大,所以直接合并就可以了

#include 
const int N = 1e5+10;
using namespace std;
int n,m;
struct treap{
	int val,key,siz,l,r;
	bool tag;
}fhq[N];
int cnt,root;

int new_treap(int val){
	fhq[++cnt].val = val;
	fhq[cnt].siz = 1;
	fhq[cnt].key = rand();
	return cnt;
}
void push_up(int pos){
	fhq[pos].siz = fhq[fhq[pos].l].siz + fhq[fhq[pos].r].siz + 1;
}
void push_down(int pos){
	if(fhq[pos].tag){
		swap(fhq[pos].l,fhq[pos].r);
		fhq[fhq[pos].l].tag ^= 1;
		fhq[fhq[pos].r].tag ^= 1;
		fhq[pos].tag = 0;
	}
}
void split(int pos, int siz , int &x, int &y){
	if(!pos){
		x = y = 0;
		return;
	}
	push_down(pos);
	if(fhq[fhq[pos].l].siz < siz){
		x = pos;
		split(fhq[pos].r,siz - fhq[fhq[pos].l].siz-1,fhq[pos].r,y);
	}else{
		y = pos;
		split(fhq[pos].l,siz,x,fhq[pos].l);
	}
	push_up(pos);
}
int merge(int x, int y){
	if(!x || !y) return x+y;
	if(fhq[x].key < fhq[y].key){
		push_down(x);
		fhq[x].r = merge(fhq[x].r,y);
		push_up(x);
		return x;
	}else{
		push_down(y);
		fhq[y].l = merge(x,fhq[y].l);
		push_up(y);
		return y; 
	}
}
void reverse(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 pos){
	if(!pos) return;
	push_down(pos);
	print(fhq[pos].l);
	cout << fhq[pos].val << " ";
	print(fhq[pos].r);
}
int main(){
	srand(time(0));
	cin >> n>> m;
	for(int i = 1;i <= n; i++){
		root = merge(root,new_treap(i));
	}
	while(m--){
		int l,r;
		cin >> l >> r;
		reverse(l,r);
	}
	print(root);
	return 0;
}

有任何疑问或不足之处,欢迎在评论区指出

你可能感兴趣的:(C++入门基础教程,随笔,学习,笔记,数据结构)