zoukankan      html  css  js  c++  java
  • 平衡树(fhq-treap,splay)

    平衡树概述

    维护树的中序遍历不变

    非旋Treap(fhq-treap)

    每个节点有自己的参数 (val) 和随机值 (ran),fhq-treap在维护中序遍历的基础上,维护随机值 (ran) 的大根堆。因为 (ran) 随机,treap的期望复杂度为(O(n log n))

    定义

    struct treap{
    	int lson,rson,val,ran,cnt/*以当前节点为根的子树大小*/;
    }tr[N];
    int tot,root;//节点数和当前根
    void update(int x){tr[x].cnt=tr[tr[x].lson].cnt+tr[tr[x].rson].cnt+1;}
    

    merge

    合并以(m1)(m2)为根的两颗子树(维护(ran)的大根堆)

    int merge(int m1,int m2){
    	if(!m1||!m2)	return m1+m2;
    	if(tr[m1].ran>=tr[m2].ran){
    		tr[m1].rson=merge(tr[m1].rson,m2);
    		update(m1);	return m1;
    	}else{
    		tr[m2].lson=merge(m1,tr[m2].lson);
    		update(m2);	return m2;
    	}
    }
    

    split

    把树断开

    将小于等于(num)的数放进以 (l) 为根的子树,大于(num)的数放进以 (r) 为根的子树(维护中序遍历不变)

    void split(int x,int num,int &l,int &r){
    	if(x==0){	l=0;	r=0;	return ;}
    	if(tr[x].val<=num){
    		l=x;
    		split(tr[x].rson,num,tr[x].rson,r);
    	}else{
    		r=x;
    		split(tr[x].lson,num,l,tr[x].lson);
    	}
    	update(x);
    	return ;
    }
    

    其它操作均在merge和split的基础上

    void insert(int x){//新建节点
    	tr[++tot].val=x;	tr[tot].ran=rand();
    	tr[tot].cnt=1;	tr[tot].lson=0;	tr[tot].rson=0;
    	int l=0,r=0;
    	split(root,x-1,l,r);
    	root=merge(merge(l,tot),r);
    }
    void delet(int x){//删除大小为x的节点
    	int l=0,r=0,pos=0;
    	split(root,x,l,r);	split(l,x-1,l,pos);
    	root=merge(merge(l,merge(tr[pos].lson,tr[pos].rson)),r);
    }
    int find_rank(int num){//查询x数的排名
    	int l=0,r=0;	split(root,num-1,l,r);	int ret=tr[l].cnt+1;
    	root=merge(l,r);	return ret;
    }
    int find_num(int rank){//查询排名为x的数
    	int pos=root;
    	while(pos){
    		if(tr[pos].cnt==1)	return tr[pos].val;
    		if(tr[tr[pos].lson].cnt>=rank)	pos=tr[pos].lson;
    		else{
    			rank-=tr[tr[pos].lson].cnt;
    			if(rank==1)	return tr[pos].val;
    			else	rank--,pos=tr[pos].rson;
    		}
    	}
    }
    int find_pre(int x){//查询前驱(小于x,且最大的数)
    	int l=0,r=0;	split(root,x-1,l,r);	int pos=l;
    	while(tr[pos].rson)	pos=tr[pos].rson;
    	root=merge(l,r);	return tr[pos].val;
    }
    int find_suf(int x){//查询后继(大于x,且最小的数)
    	int l=0,r=0;	split(root,x,l,r);	int pos=r;
    	while(tr[pos].lson)	pos=tr[pos].lson;
    	root=merge(l,r);	return tr[pos].val;
    }
    

    例题

    P1486 [NOI2004]郁闷的出纳员

    1. (I命令)输入(k),若(k>= min\_salary), 则插入(k)
    2. (A命令)将每个数加上(k)
    3. (S命令)将每个数减去(k),并删除所有(<min)的数
    4. (F命令)查询第(k)大的数

    A和S命令显然不会改变平衡树中各数的相对位置,但显然不能一个一个改

    记一个变化量(delta),每次加减直接加减(delta)即可。

    为了维护各数的相对大小,在插入时应该插入(val=k-delta)

    查询时,(k+Delta <min\_salary)的所有(k)被删除((Delta)为区间内变化量),即(k-delta_{pre}+delta_{now}<min\_salary),即(val<min\_salary-delta)(val)会被删掉

    Splay

    优点:可以指定哪一个点当根

    基本操作:rotate和splay

    rotate

    在不破坏平衡树结构(中序遍历不变的情况下),将(x)向上旋转一次

    其中的一种情况:

    (id(x))表示(x)是其父亲的哪个儿子

    inline bool id(int x){return tr[tr[x].fa].son[1]==x;}
    

    规律如下:

    • 自己的(id(x) xor 1)儿子变成父亲的(id(x))儿子(自己的另外一边儿子替换自己的位置)
    • 自己接上祖父
    • 原来的父亲变成自己的(id(x) xor 1)儿子(即之前失去的儿子)
    inline void connect(int x,int y,int pos){tr[x].fa=y;tr[y].son[pos]=x;}
    inline void rotate(int x){
    	int f=tr[x].fa,gf=tr[f].fa;
    	int id1=id(f),id2=id(x);
    	connect(tr[x].son[id2^1],f,id2);
    	connect(f,x,id2^1);
    	connect(x,gf,id1);
    	update(f);update(x);
    }
    

    splay

    一条链的情况会大大影响时间复杂度

    我们需要通过这个操作减小链的长度

    对于三点共线的情况,连续旋转是无效的

    这时我们需要先旋转它的父亲,再旋转自己

    否则就连续旋转两次自己

    inline void splay(int x,int to){
    	while(tr[x].fa!=to){
    		if(tr[tr[x].fa].fa!=to){
    			if(id(x)==id(tr[x].fa))rotate(tr[x].fa);
    			else rotate(x);
    		}
    		rotate(x);
    	}
    	if(to==0)rt=x;
    }
    

    (splay(x,y))的含义为:让(x)成为(y)的一个儿子,当(y=0)时,即让(x)成为根

    没事就splay一下是好习惯,而且splay操作还有(update)的效果

    其他操作都在这两个的基础上,但是比fhq-treap要复杂一些

    (find(x))

    找到(x)所在节点,若不存在则返回离它最近的(>)它或(<)它的节点

    inline int find(int x){
    	int pos=rt;
    	while(tr[pos].val!=x&&tr[pos].son[tr[pos].val<x])pos=tr[pos].son[tr[pos].val<x];
    	splay(pos,0);return rt;
    }
    

    (insert(x))

    插入一个值为(x)的节点

    已有则直接插入,没有则找到应该插入的位置

    inline void insert(int x){
    	int pos=rt,fa=0;
    	while(tr[pos].val!=x&&pos)fa=pos,pos=tr[pos].son[tr[pos].val<x];
    	if(pos)++tr[pos].cnt;
    	else{
    		pos=++cnt;tr[cnt].cnt=tr[cnt].size=1;tr[cnt].val=x;tr[cnt].son[0]=tr[cnt].son[1]=0;
    		connect(cnt,fa,tr[fa].val<x);
    	}
    	splay(pos,0);
    }
    

    (pre(x))

    找到(x)的前驱所在的节点

    inline int pre(int x){
    	int pos=find(x);
    	if(tr[pos].val<x)return pos;
    	pos=tr[pos].son[0];
    	while(pos&&tr[pos].son[1])pos=tr[pos].son[1];
    	splay(pos,0);
    	return rt;
    }
    

    先找,如果找到的是小于自己的,那么就一定是小于自己的最大的,直接返回。

    否则先变成小于自己的,再一直向右找到最大的

    (suf(x))

    找到(x)的后继所在的节点

    inline int suf(int x){
    	int pos=find(x);
    	if(tr[pos].val>x)return pos;
    	pos=tr[pos].son[1];
    	while(pos&&tr[pos].son[0])pos=tr[pos].son[0];
    	splay(pos,0);
    	return rt;
    }
    

    原理与(pre)类似

    (delete(x))

    删除一个值为(x)的节点

    inline void delet(int x){
    	int u=pre(x),v=suf(x);
    	splay(u,0);splay(v,u);
    	int cur=tr[v].son[0];
    	if(tr[cur].cnt>1){--tr[cur].cnt;splay(cur,0);}
    	else{tr[cur].size=tr[cur].cnt=0;tr[v].son[0]=0;update(v);update(u);}
    }
    

    先找到比自己的前驱和后继

    然后以前驱为根,把后继接到前驱的儿子上(一定是右儿子),那么自己就一定在后继和前驱中间,即后继的左儿子

    如果个数(>1),那么直接减个数即可

    否则把这一条边断掉

    查询排名和排名为(x)的数

    inline int query_by_rank(int rank){
    	int pos=rt;
    	while(rank){
    		if(rank<=tr[tr[pos].son[0]].size)pos=tr[pos].son[0];
    		else if(rank>tr[tr[pos].son[0]].size+tr[pos].cnt)rank-=tr[tr[pos].son[0]].size+tr[pos].cnt,pos=tr[pos].son[1];
    		else{splay(pos,0);return tr[pos].val;}
    	}
    }
    inline int query_rank(int val){
    	int pos=pre(val);
    	return tr[pos].cnt+tr[tr[pos].son[0]].size;
    }
    

    根fhq-treap类似

  • 相关阅读:
    求C的近似值
    判断是否直角三角形
    温度转换异常处理
    python html页面
    python 爬虫goole主页
    python 足球模拟
    python模拟羽毛球竞技
    python 读书报告
    python 用jieba分词统计关于红楼梦的高频词
    python 在终端输出如下信息
  • 原文地址:https://www.cnblogs.com/harryzhr/p/14255533.html
Copyright © 2011-2022 走看看