fhq treap

fhq-treap 小结

粗浅地学习了这个神奇的数据结构,下面瞎写一些感受

首先fhq treap是一个基于分裂与合并的平衡树,那么怎么分裂,怎么合并呢

我们分两种情况考虑

一、权值平衡树(我自己取的名字)

所谓权值平衡树,就是任何操作都只与权值有关的平衡树

比如最基础的分裂,合并操作

分裂就是把平衡树按照权值\(k\)分成两半,一边所有点的权值\(\leq k\),另一边权值\(\gt k\)

怎么分裂呢

首先根据\(treap\)的定义,所有点的权值是一颗二叉搜索树(BST),就是左边比他小,右边比他大

那么分裂的时候,我们比较当前节点和\(k\)的大小,如果比\(k\)大,那么显然右子树内全部节点都比\(k\)

所以我们把当前节点加上他的右子树连到第二棵树上,剩下的左子树继续分裂

如果比\(k\)小也是一样的

Example Code:
void split(int &x,int &y,int k,int nw){
   //x表示第一棵子树上的某个节点的某个指针,y是第二棵子树上的,nw是当前节点
   if(nw==0){x=y=0;return;}
   if(tr[x].val>k){
      y=nw;split(x,tr[nw].l,k,tr[nw].l);
   }
   else{
      x=nw;split(tr[nw].r,y,k,tr[nw].r);
   }
   update(nw);
}

然后合并就更简单了

就是把两颗子树(默认其中有一棵每个点的权值都小于另一棵任何点)合成一个

因为有上面那个条件,所以只考虑那个随机值的大小使得树满足堆的性质即可

那么比较当前两棵树中的两个当前节点:

如果第一棵树的rand值小,那么这个点连到总树上,然后这个点的左子树肯定不受到第二棵子树的影响(权值小于第二棵子树),那么递归合并这个点的右子树和第二棵子树即可

反之亦然

Example Code:
int merge(int x,int y){
   if(!x || !y) return x+y; //精妙的写法,避免一堆if
   if(tr[x].rnd<tr[y].rnd){
      tr[x].r=merge(tr[x].r,y);update(x);
      return x;
   }
   else{
      tr[y].l=merge(x,tr[y].l);update(y);
      return y;
   }
}

这两种操作都是\(O(深度)\)的,所以复杂度是\(\log\)级别的

那么其他的操作就是两个操作拼凑起来

比如插入一个数,就是先分裂成小于等于这个数的一部分,大于这个数的一部分,然后当前数当做第三部分,做\(merge(merge(l,nw),r)\)即可

比如删除一个数,就是先分裂成小于等于这个数的一部分,大于这个数的一部分,然后再把第一棵树分成等于这个数和小于这个数,然后在等于这个数的树里把根删掉(merge根的两个子树),最后全部合起来就可以了

再比如求排名,可以分出小于这个数的一部分,这个部分的size就是排名了

等等。。。

但是有一个问题,就是常数比较大

比如删除操作常数可能到5~6,比起什么线段树,树状数组之类的慢多了

但是在平衡树里面算快的了。。。(垃圾数据结构)

但是这样怎么处理区间赋值,区间加这种操作呢?没有办法。。。

因为你在处理的时候一直保持有序状态,顺序就乱套了,没法取出区间

所以我们引入第二类fhq-treap

二、序列操作平衡树(仍旧是我自己取的名字。。。)

这个平衡树的时间复杂度很奇怪,反正我觉得不是\(\log\)的,但是好像跑的挺快

有没有人会证复杂度的请留言,谢谢

好吧现在我大概会证了

其实这个跟上面那个是一个东西

我们大概可以理解为把下标作为val值,权值作为一个新的变量

然后因为下标肯定是连续的1~n,所以子树大小也就是下标了

所以我们不需要记录下标,可以直接用size当做下标使用,这样就和上面那个完全一样了

这个和上面那个的唯一区别就是他不满足BST的性质(或者满足但是我没有发现?)

首先我们观察他的build函数(就是把一个无序数组\(O(n)\)建成treap)

int build(int *data,int k){
	int nw=0,pre=0;top=0;
	rep(i,1,k){
		nw=newnode(data[i]);pre=0;
		while(top && rnd[sta[top]]>rnd[nw]){
			update(sta[top]);
			pre=sta[top];
			sta[top--]=0;
		}
		if(top) son[sta[top]][1]=nw;
		son[nw][0]=pre;sta[++top]=nw;
	}
	while(top) update(sta[top--]);
	return sta[1];
}

其中data就是这个数组,k是他的长度

sta维护的是一个栈,top是栈顶指针

newnode(x)是新建一个权值为x的节点并且返回下标

这是什么意思呢

就是说我们只关心随机值,保持他是个堆

那么我们顺着插入每一个元素

在栈里存储最右链,栈顶存储最底下的元素,栈底存储根,那么rand值显然从栈顶到栈底递减,然后我们在栈里找,直到找到第一个rand小于当前rand的元素,那么这个元素就是当前元素的父亲

然后我们假装数组是有序的,那么新加进去的元素肯定最大,所以他就是他父亲的右儿子

他父亲原来的右子树变成他的左子树(权值比他小)

然后更新最右链

从中我们显然发现不满足BST的性质(或者说下标满足BST的性质,但是我们存储的值是权值而非下标,也就是说下标并没记录,在操作几次之后就全乱了),那么复杂度是怎么回事啊。。。

先不说复杂度的事了,我们先看有哪些操作

最基础的依旧是split和merge

void split(int &x,int &y,int k,int nw){
	if(nw==0){x=y=0;return;}
	pushdown(nw);
	if(sz[son[nw][0]]>=k){
		y=nw;split(x,son[nw][0],k,son[nw][0]);
	}
	else{
		x=nw;split(son[nw][1],y,k-sz[son[nw][0]]-1,son[nw][1]);
	}
	update(nw);
}

int merge(int x,int y){
	if(x) pushdown(x);if(y) pushdown(y);
	if(!x || !y) return x+y;
	if(rnd[x]<rnd[y]){
		son[x][1]=merge(son[x][1],y);
		update(x);return x;
	}
	else{
		son[y][0]=merge(x,son[y][0]);
		update(y);return y;
	}
}

我们看跟之前的区别

实际上区别不太大,就是把split中val的比较变成了子树大小的比较,传进去的k指的是位置而非权值

然后注意到多了一个pushdown函数

什么鬼。。。这不是线段树操作吗。。。

好的,为了处理区间操作,我们引入了懒标记(好强啊),因为我们的treap不再按val分而是按下标分,所以一个节点的儿子也是他的子区间,和线段树类似,所以我们可以完成懒标记的下放操作

void pushdown(int x){
	if(son[x][0]) change(son[x][0],lazy[x]);
	if(son[x][1]) change(son[x][1],lazy[x]);
	lazy[x]=0;
}

事实上现在我们就讲完了所有基础操作了,可以做很多题了,就是把这些操作拼拼凑凑即可

下面我们看一道例题:

bzoj1500 维修数列

请写一个程序,要求维护一个数列,支持以下 6 种操作:

请注意,格式栏 中的下划线‘ _ ’表示实际输入文件中的空格

img

我们一个个操作分析

首先插入操作

他并不插入一个数,他插入一堆数。。。

怎么办呢

首先插入总次数是有限制的,所以每次暴力插入应该是可行的,(不过因为复杂度本身就很奇怪所以t了也是正常的事)

但是我们有更好的插入方法

插入一个的时候,我们是merge三棵树,现在也一样嘛,就是先把这个序列建成一个treap,然后一起merge起来

因为他插入位置是连续的一段,所以merge是正确的

然后删除操作

就是拆成三颗子树,然后把中间的扔掉

然后修改

记录标记表示这个区间被修改成几了,如果为inf则没被修改

那么只要拆成三颗树,然后把根打一下标记就好了

翻转

先拆成三棵树

然后把中间的树每一层都翻转

这样就炸了

所以我们打上一个翻转标记,翻转一次就xor 1

求和

sum是很好维护的,因为修改的时候只要记录size就可以维护了,翻转的时候总的sum不变

求最大子序列

这个有点麻烦

维护三个量,首先lmx表示这个节点对应区间的左边一整段的最大和,rmx就是右边一整段的最大和,tmx表示经过这个节点的根的最大和

然后tmx[root]就是答案

更新详见代码

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<long long,long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i,j,k)  for(register int i=(int)(j);i<=(int)(k);i++)
#define rrep(i,j,k) for(register int i=(int)(j);i>=(int)(k);i--)

ll read(){
	ll x=0,f=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=x*10+c-'0';c=getchar();}
	return x*f;
}

const int inf=1000000000;
const int maxn=1000100;
queue<int> trash;int n,m,a[maxn];
int sta[maxn],son[maxn][2],sz[maxn],sum[maxn],val[maxn],tmx[maxn],lmx[maxn],rmx[maxn];
int cnt,lazy_revise[maxn],lazy_reverse[maxn],rnd[maxn],top,aa[maxn],root;

void update(int x){
	int l=son[x][0],r=son[x][1];
	if(l && r){
		sz[x]=sz[l]+sz[r]+1;
		sum[x]=sum[l]+sum[r]+val[x];
		tmx[x]=max(tmx[l],tmx[r]);
		tmx[x]=max(tmx[x],rmx[l]+val[x]+lmx[r]);
		lmx[x]=max(lmx[l],sum[l]+val[x]+lmx[r]);
		rmx[x]=max(rmx[r],rmx[l]+val[x]+sum[r]);
	}
	else if(l){
		sz[x]=sz[l]+1;
		sum[x]=sum[l]+val[x];
		tmx[x]=max(tmx[l],rmx[l]+val[x]);
		lmx[x]=max(max(lmx[l],sum[l]+val[x]),0);
		rmx[x]=max(0,val[x]+rmx[l]);
	}
	else if(r){
		sz[x]=sz[r]+1;
		sum[x]=sum[r]+val[x];
		tmx[x]=max(tmx[r],lmx[r]+val[x]);
		rmx[x]=max(rmx[r],sum[r]+val[x]);
		rmx[x]=max(0,rmx[x]);
		lmx[x]=max(0,lmx[r]+val[x]);
	}
	else{
		sz[x]=1;
		sum[x]=tmx[x]=val[x];
		lmx[x]=rmx[x]=max(val[x],0);
	}
}

int newnode(int k){
	int x=0;
	if(!trash.empty()){
		x=trash.front();trash.pop();
	}
	else x=++cnt;
	son[x][0]=son[x][1]=lazy_reverse[x]=0;
	lazy_revise[x]=inf;
	rnd[x]=rand();sz[x]=1;val[x]=sum[x]=tmx[x]=k;
	lmx[x]=rmx[x]=max(k,0);return x;
}

int build(int *data,int k){
	int nw=0,pre=0;top=0;
	rep(i,1,k){
		nw=newnode(data[i]);pre=0;
		while(top && rnd[sta[top]]>rnd[nw]){
			update(sta[top]);
			pre=sta[top];
			sta[top--]=0;
		}
		if(top) son[sta[top]][1]=nw;
		son[nw][0]=pre;sta[++top]=nw;
	}
	while(top) update(sta[top--]);
	return sta[1];
}

void doit(int x){
	if(x==0) return;
	trash.push(x);
	doit(son[x][0]),doit(son[x][1]);
}

void change(int x,int k){
	val[x]=k;sum[x]=sz[x]*k;
	lmx[x]=rmx[x]=max(sum[x],0);
	tmx[x]=max(sum[x],val[x]);
	lazy_revise[x]=k;
}

void flip(int x){
	swap(son[x][0],son[x][1]);
	swap(lmx[x],rmx[x]);
	lazy_reverse[x]^=1;
}

void pushdown(int x){
	if(lazy_revise[x]!=inf){
		if(son[x][0]) change(son[x][0],lazy_revise[x]);
		if(son[x][1]) change(son[x][1],lazy_revise[x]);
	}
	if(lazy_reverse[x]!=0){
		if(son[x][0]) flip(son[x][0]);
		if(son[x][1]) flip(son[x][1]);
	}
	lazy_revise[x]=inf;lazy_reverse[x]=0;
}

void split(int &x,int &y,int k,int nw){
	if(nw==0){x=y=0;return;}
	pushdown(nw);
	if(sz[son[nw][0]]>=k){
		y=nw;split(x,son[nw][0],k,son[nw][0]);
	}
	else{
		x=nw;split(son[nw][1],y,k-sz[son[nw][0]]-1,son[nw][1]);
	}
	update(nw);
}

int merge(int x,int y){
	if(x) pushdown(x);if(y) pushdown(y);
	if(!x || !y) return x+y;
	if(rnd[x]<rnd[y]){
		son[x][1]=merge(son[x][1],y);
		update(x);return x;
	}
	else{
		son[y][0]=merge(x,son[y][0]);
		update(y);return y;
	}
}

void ins(){
	int pos=read(),len=read();
	rep(i,1,len) aa[i]=read();
	int nw=build(aa,len);
	int x,y;
	split(x,y,pos,root);
	root=merge(merge(x,nw),y);
}

void del(){
	int pos=read(),len=read();
	int x,y,z,w;
	split(x,y,pos-1,root);
	split(z,w,len,y);
	root=merge(x,w);
	doit(z);
}

void upd(){
	int pos=read(),len=read(),k=read();
	int x,y,z,w;
	split(x,y,pos-1,root);
	split(z,w,len,y);
	change(z,k);root=merge(x,merge(z,w));
}

void rev(){
	int pos=read(),len=read();
	int x,y,z,w;
	split(x,y,pos-1,root);
	split(z,w,len,y);
	flip(z);root=merge(x,merge(z,w));
}

void calc1(){
	int pos=read();
	int len=read();
	int x,y,z,w;
	split(x,y,pos-1,root);
	split(z,w,len,y);
	printf("%d\n",sum[z]);
	root=merge(x,merge(z,w));
}

void calc2(){
	printf("%d\n",tmx[root]);
}

int main(){
	srand(20020709);
	n=read();m=read();
	rep(i,1,n) a[i]=read();
	root=build(a,n);
	while(m--){
		string s;cin>>s;
		if(s[0]=='I') ins();
		else if(s[0]=='D') del();
		else if(s[0]=='M' && s[2]=='K') upd();
		else if(s[0]=='R') rev();
		else if(s[0]=='G') calc1();
		else calc2();
	}
	return 0;
}
posted @ 2018-09-05 22:05  wawawa8  阅读(510)  评论(0编辑  收藏  举报