fhq_treap学习笔记

一、前言

\(fhq-treap\),即无旋\(treap\),代码量不大,且可持久化,是平衡树的重要实现方法。

\(fhq-treap\)主要操作仅有两个:分裂\((split)\),合并\((merge)\)。

至于平衡树的性质,可以理解为一棵高度永远为\(log(节点数)\)的二叉查找树\((BST)\)

二、主要操作(码风不喜勿喷)

\(1\)、变量定义

见注释:

struct treap{
        int val,ls,rs,rd,sz;    //val:点权,ls:左儿子,rs:右儿子,rd:优先级(rand出的),sz:子树大小(含本身)
}t[N];

\(2、split\)

本质是将一棵平衡树分成两棵,有两种分法。

\(1\))按权值分配,将所有小于\(x\)的权值归为一棵树,剩下的为另一棵树,上代码:

il void split(int u,int x,int &rt1,int &rt2){    //rt1:所有点权均小于x的树的根,rt2:另一棵树的根
	if(!u){
		rt1=rt2=0;
		return;
	}
	if(t[t[u].ls].sz>=x){
		rt2=u;
		split(t[u].ls,x,rt1,t[u].ls);
	}
	else{
		rt1=u;
		split(t[u].rs,x,t[u].rs,rt2);
	}
	t[u]=t[t[u].ls].sz+t[t[u].rs].sz+1;
}

对于每个非叶子节点\(u\),下一次遍历必到它的左/右儿子,由于平衡树高度为\(logn\),故复杂度为\(O(logn)\)

\(2\))按前\(x\)个分,与上述类似,复杂度也是\(O(logn)\),每次遍历右儿子时将\(x\)减去左儿子的\(size+1\)(节点本身)即可

il void split(int u,int x,int &rt1,int &rt2){    //rt1:所有点权均小于x的树的根,rt2:另一棵树的根
	if(!u){
		rt1=rt2=0;
		return;
	}
	if(t[t[u].ls].sz>=x){
		rt2=u;
		split(t[u].ls,x,rt1,t[u].ls);
	}
	else{
		rt1=u;
		split(t[u].rs,x-t[t[u].ls].sz-1,t[u].rs,rt2);
	}
	t[u]=t[t[u].ls].sz+t[t[u].rs].sz+1;
}

3、\(merge\)

与可并堆的合并类似,具体的看代码:

int merge(int x,int y){
	if(!x || !y)
		return x+y;
	if(t[x].rd<t[y].rd){
		t[x].rs=merge(t[x].rs,y);
		t[x].sz=t[t[x].ls].sz+t[t[x].rs].sz+1;
		return x;
	}
	else{
		t[y].ls=merge(x,t[y].ls);
		t[y].sz=t[t[y].ls].sz+t[t[y].rs].sz+1;
		return y;
	}
}

三、其他操作

1、\(insert\)

插入一个权值为\(x\)的点,只需按照与\(x\)的大小关系将树\(split\),再将此点作为权值小的树的根,最后\(merge\)。

2、\(delete\)

删除一个权值为\(x\)的点,先把树按照与\(x\)的大小\(split\)为树1(含\(x\))和树2,再把树1按照与\(x-1\)的大小\(split\)为树3和树4,再把树\(2\)、\(3\)、\(4\)合并。

四、例题

1、\(BZOJ3224\) 普通平衡树

传送门

\(fhq-treap\)基本应用。

\(code:\)挖坑

2、\(BZOJ3223\) 文艺平衡树

传送门

区间翻转操作,但与普通平衡树差距不大。记得及时下放\(tag\)。

\(code:\)

/********************************
    This code is made by ghy.
    Oj: Bzoj
    Language: C++
    Status: AC
    Complexity: O(nlogn)
********************************/
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <ctime>
#define il inline
#define ll long long
#define rg register
using namespace std;
il int read(){
	rg int x=0,f=1; rg char c=getchar();
	while(c<'0' || c>'9'){
		if(c=='-') f=-1; c=getchar();
	}
	while(c>='0' && c<='9'){
		x=(x<<1)+(x<<3)+(c^48); c=getchar();
	}
	return x*f;
}

const int N=1e5+5;
int n,m,l,r,rt,cnt;
bool flg;
struct treap{
	int val,ls,rs,rd,sz;
	bool tag;
}t[N];

il void up(int u){
	t[u].sz=t[t[u].ls].sz+t[t[u].rs].sz+1;
}

il void down(int u){
	if(!t[u].tag || !u)
		return;
	t[u].ls^=t[u].rs^=t[u].ls^=t[u].rs;
	t[t[u].ls].tag^=1;
	t[t[u].rs].tag^=1;
	t[u].tag=0;
}

il int add(int x){
	t[++cnt].val=x;
	t[cnt].sz=1;
	t[cnt].rd=rand();
	return cnt;
}

il void split(int u,int x,int &rt1,int &rt2){
	if(!u){
		rt1=rt2=0;
		return;
	}
	down(u);
	if(t[t[u].ls].sz>=x){
		rt2=u;
		split(t[u].ls,x,rt1,t[u].ls);
	}
	else{
		rt1=u;
		split(t[u].rs,x-t[t[u].ls].sz-1,t[u].rs,rt2);
	}
	up(u);
}

il int merge(int x,int y){
	if(!x || !y)
		return x+y;
	down(x);
	down(y);
	if(t[x].rd<t[y].rd){
		t[x].rs=merge(t[x].rs,y);
		up(x);
		return x;
	}
	else{
		t[y].ls=merge(x,t[y].ls);
		up(y);
		return y;
	}
}

il void rev(int l,int r){
	int rt1=0,rt2=0,rt3=0,rt4=0;
	split(rt,r,rt1,rt2);
	split(rt1,l-1,rt3,rt4);
	t[rt4].tag^=1; 
	rt=merge(merge(rt3,rt4),rt2);
}

il void dfs(int u){
	if(!u)
		return;
	down(u);
	dfs(t[u].ls);
	printf("%d ",t[u].val);
	dfs(t[u].rs);
}

int main(){
	srand(time(0));
	n=read();
	m=read(); 
	for(rg int i=1;i<=n;++i)
		rt=merge(rt,add(i));
	while(m--){
		l=read();
		r=read();
		rev(l,r);
	}
	dfs(rt);
	return 0;
}
posted @ 2020-02-05 15:25  cjghy  阅读(176)  评论(0)    收藏  举报