替罪羊树 知识总结

替罪羊树 知识总结

替罪羊树是一类神奇的平衡树。它最神奇的地方就在于,大部分平衡树都是用愚蠢的单旋来维护平衡,而fhq-Treap则是用split和merge维护平衡的,可替罪羊树却是用一种神奇的操作维护平衡的,那就是重构Rebuild。每次插入和删除元素的时候,检查子树大小,若失衡则直接重购以维护整棵树平衡。
在讲思路前,我们先要注意一个点:替罪羊树仅能用重构来维持平衡,因而若节点被删除后,无法将其高效的移除,所以我们采取懒惰删除,每次仅将节点的计数器--即可。
先来看一下我们的变量区和基础的函数吧:
struct node{
	int l,r;//左右儿子
	int sd,sz;//sd为节点所在子树的实际大小,而sz则为包括所有删除的元素在内的子树大小
	int cnt,val;//cnt为数量,val为键值
};
inline void update(int p)
{
	f[p].sd=f[f[p].l].sd+f[f[p].r].sd+f[p].cnt;
	f[p].sz=f[f[p].l].sz+f[f[p].r].sz+f[p].cnt;
}
接下来我们来看看重构操作吧。我们预先设定一个阈值α,一般取[0.7,0.8]左右,若检查到某个节点的儿子所在子树的sz占这个节点sz的比例超过α时,就重构。由于当被删除节点过多时,搜索树效率也会显著降低,于是当一个节点的sd占其sz的比例超过α时,亦重构。
inline bool Can_Rbu(int p)
{
	return f[p].cnt&&(f[p].sz*alpha<=(double)max(f[f[p].l].sz,f[f[p].r].sz)||f[p].sz*alpha>=(double)f[p].sd);
}
那么怎么重构呢?我们已经知道了,对于一棵平衡树,它的中序遍历是排好序的。所以我们把它的中序遍历拉到数组中,然后重新二分建树即可:
int tmp[100010];
void Rbu_Flatten(int &num,int p)
{
	if(!p) return;//空儿子返回
	Rbu_Flatten(num,f[p].l);//搜左儿子
	if(f[p].cnt) tmp[num++]=p;//加入当前节点
	Rbu_Flatten(num,f[p].r);//搜右儿子
}
int Rbu_Build(int l,int r)
{
	if(l>=r) return 0;//二分边界
	int mid=(l+r)>>1;
	f[tmp[mid]].l=Rbu_Build(l,mid);//建左儿子
	f[tmp[mid]].r=Rbu_Build(mid+1,r);//建右儿子
	update(tmp[mid]);//更新信息
	return tmp[mid];
}
void Rbu(int &p)
{
	int x=0;
	Rbu_Flatten(x,p);
	p=Rbu_Build(0,x);//这里注意下标从0开始可以节省很多事情
}
以上就是基本函数啦!其余的部分神似普通的平衡树,我们的介绍和注释就稍微简略一些了咯。
首先是插入和删除。我们在遍历路径上每一个节点时,都检查是否满足重构条件,并重构即可。其余与普通二叉搜索时雷同。
void Insert(int &p,int val)
{
	if(!p)
	{
		p=++tot;
		f[p].cnt=f[p].sd=f[p].sz=1;
		f[p].val=val;
		f[p].l=f[p].r=0;
	}
	else
	{
		if(f[p].val==val) f[p].cnt++;
		else if(val<f[p].val) Insert(f[p].l,val);
		else Insert(f[p].r,val);
		update(p);
		if(Can_Rbu(p)) Rbu(p);
	}
}
void del(int &p,int val)
{
	if(!p) return;
	f[p].sd--;
	if(f[p].val==val)
	{
		if(f[p].cnt) f[p].cnt--;
	}
	else
	{
		if(val<f[p].val) del(f[p].l,val);
		else del(f[p].r,val);
		update(p);
	}
	if(Can_Rbu(p)) Rbu(p);
}
接下来是由权值查询对应的值。依旧是与普通二叉搜索树完全相同的操作,在此不再赘述了,不懂直接看代码吧QAQ
int GetVal(int p,int rk)
{
	if(!p) return 0;
	if(rk>f[f[p].l].sd&&rk<=f[f[p].l].sd+f[p].cnt) return f[p].val;
	else if(rk>f[f[p].l].sd+f[p].cnt) return GetVal(f[p].r,rk-f[f[p].l].sd-f[p].cnt);
	else return GetVal(f[p].l,rk);
}
最后是查询前后驱的函数。在此我们稍微的改变一下函数的返回值:将返回数值改为返回下标。这样以后查询排名就可以用前驱坐标+1即可。代码依旧与普通平衡树相同……
int Get_uprb(int p,int val)
{
	if(!p) return 1;
	if(f[p].val==val&&f[p].cnt) return f[f[p].l].sd+f[p].cnt+1;
	else if(val<f[p].val) return Get_uprb(f[p].l,val);
	else return f[f[p].l].sd+f[p].cnt+Get_uprb(f[p].r,val);
}
int Get_lwrb(int p,int val)
{
	if(!p) return 0;
	if(f[p].val==val&&f[p].cnt) return f[f[p].l].sd;
	else if(val<f[p].val) return Get_lwrb(f[p].l,val);
	else return f[f[p].l].sd+f[p].cnt+Get_lwrb(f[p].r,val);
}
最后就是完整版代码啦:
#include<bits/stdc++.h>
using namespace std;
template <typename T>
void read(T &x) {
    x = 0;
    int f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = x * 10 + (ch ^ 48);
        ch = getchar();
    }
    x *= f;
    return;
}
template <typename T>
void write(T x)
{
    if(x < 0) {
        putchar('-');
        x = -x;
    }
    if(x > 9)
        write(x/10);
    putchar(x % 10 + '0');
    return;
}
int n;
int rt,tot=0;
struct node{
	int l,r;
	int sd,sz;
	int cnt,val;
}f[100010];
const double alpha=0.75;
inline void update(int p)
{
	f[p].sd=f[f[p].l].sd+f[f[p].r].sd+f[p].cnt;
	f[p].sz=f[f[p].l].sz+f[f[p].r].sz+f[p].cnt;
}
inline bool Can_Rbu(int p)
{
	return f[p].cnt&&(f[p].sz*alpha<=(double)max(f[f[p].l].sz,f[f[p].r].sz)||f[p].sz*alpha>=(double)f[p].sd);
}
int tmp[100010];
void Rbu_Flatten(int &num,int p)
{
	if(!p) return;
	Rbu_Flatten(num,f[p].l);
	if(f[p].cnt) tmp[num++]=p;
	Rbu_Flatten(num,f[p].r);
}
int Rbu_Build(int l,int r)
{
	if(l>=r) return 0;
	int mid=(l+r)>>1;
	f[tmp[mid]].l=Rbu_Build(l,mid);
	f[tmp[mid]].r=Rbu_Build(mid+1,r);
	update(tmp[mid]);
	return tmp[mid];
}
void Rbu(int &p)
{
	int x=0;
	Rbu_Flatten(x,p);
	p=Rbu_Build(0,x);
}
void Insert(int &p,int val)
{
	if(!p)
	{
		p=++tot;
		f[p].cnt=f[p].sd=f[p].sz=1;
		f[p].val=val;
		f[p].l=f[p].r=0;
	}
	else
	{
		if(f[p].val==val) f[p].cnt++;
		else if(val<f[p].val) Insert(f[p].l,val);
		else Insert(f[p].r,val);
		update(p);
		if(Can_Rbu(p)) Rbu(p);
	}
}
void del(int &p,int val)
{
	if(!p) return;
	f[p].sd--;
	if(f[p].val==val)
	{
		if(f[p].cnt) f[p].cnt--;
	}
	else
	{
		if(val<f[p].val) del(f[p].l,val);
		else del(f[p].r,val);
		update(p);
	}
	if(Can_Rbu(p)) Rbu(p);
}
int Get_uprb(int p,int val)
{
	if(!p) return 1;
	if(f[p].val==val&&f[p].cnt) return f[f[p].l].sd+f[p].cnt+1;
	else if(val<f[p].val) return Get_uprb(f[p].l,val);
	else return f[f[p].l].sd+f[p].cnt+Get_uprb(f[p].r,val);
}
int Get_lwrb(int p,int val)
{
	if(!p) return 0;
	if(f[p].val==val&&f[p].cnt) return f[f[p].l].sd;
	else if(val<f[p].val) return Get_lwrb(f[p].l,val);
	else return f[f[p].l].sd+f[p].cnt+Get_lwrb(f[p].r,val);
}
int GetVal(int p,int rk)
{
	if(!p) return 0;
	if(rk>f[f[p].l].sd&&rk<=f[f[p].l].sd+f[p].cnt) return f[p].val;
	else if(rk>f[f[p].l].sd+f[p].cnt) return GetVal(f[p].r,rk-f[f[p].l].sd-f[p].cnt);
	else return GetVal(f[p].l,rk);
}
int main()
{
	read(n);
	for(int i=1;i<=n;i++)
	{
		int op;
		read(op);
		int x;
		read(x);
		switch(op)
		{
			case 1:
				Insert(rt,x);break;
			case 2:
				del(rt,x);break;
			case 3:
				write(Get_lwrb(rt,x)+1);putchar('\n');break;
			case 4:
				write(GetVal(rt,x));putchar('\n');break;
			case 5:
				write(GetVal(rt,Get_lwrb(rt,x)));putchar('\n');break;
			case 6:
				write(GetVal(rt,Get_uprb(rt,x)));putchar('\n');break;
		}
	}
	return 0;
}
posted @ 2020-01-07 19:41  xiaoh105  阅读(103)  评论(0编辑  收藏  举报