Warning: session_start(): open(/tmp/sess_40d2af7521ee4b1ac40f56874a65955b, O_RDWR) failed: No space left on device (28) in /data/wiki/inc/init.php on line 239

Warning: session_start(): Failed to read session data: files (path: ) in /data/wiki/inc/init.php on line 239

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/inc/auth.php on line 430

Warning: mkdir(): No space left on device in /data/wiki/lib/plugins/dw2pdf/vendor/mpdf/mpdf/src/Cache.php on line 19
Temporary files directory "/data/wiki/data/tmp/dwpdf/715/" is not writable

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/inc/actions.php on line 38

Warning: Cannot modify header information - headers already sent by (output started at /data/wiki/inc/init.php:239) in /data/wiki/lib/tpl/dokuwiki/main.php on line 12
2020-2021:teams:legal_string:jxm2001:替罪羊树 [CVBB ACM Team]

用户工具

站点工具


2020-2021:teams:legal_string:jxm2001:替罪羊树

这是本文档旧的修订版!


替罪羊树

算法简介

一种平衡树,思维简单,码量少,速度还行,而且没有旋转操作。

算法思想

首先替罪羊树的插入类似普通的二叉搜索树,删除操作就是打上删除标记。

但只有这样显然不能保证替罪羊树的平衡度。

替罪羊树的核心操作是重构操作,当树的不平衡度大于一个范围时就考虑对一棵子树进行重构。

重构方法为中序遍历子树,将没有打上删除标记的结点加入序列。得到一个有序序列,然后用类似线段树建树的方法重新建树。

重构操作虽然单次复杂度为 $O(n)$,但可以用势函数方法证明均摊复杂度为 $O(\log n)$,具体证明方法自行百度。

问题在于何时考虑重构。

考虑维护每个结点所在子树的未被删除的结点个数 $\text{sz}$ 和结点总数 $\text{tot}$。

引入一个平衡因子 $\alpha$ 值,当 $\alpha\ast\text{sz} \lt \max(\text{sz}_{lson},\text{sz}_{rson})$ 时考虑重构。

$\alpha$ 过大将导致树的平衡度较差,查询效率低。 $\alpha$ 过小将导致树的重构次数过多,插入、删除效率低。

因此 $\alpha$ 值通常会设置成 $0.7 \sim 0.8$,可以根据题目要求自行调整。

同时,如果一棵树上被删除的无效结点过多,也会影响查找效率,所以也需要重构。

这里设置 $\beta$ 值为 $0.4$,当 $\beta\ast\text{tot} \gt \text{sz}$ 时考虑重构。

插入或删除结束后需要检查从根结点到插入结点的路径是否有结点需要重构,不能检查从插入结点到根结点的路径。

因为一个结点的重构不能影响该结点的祖先结点的平衡度,所以如果该结点的祖先结点需要重构,则重构该结点没有意义。

代码模板

洛谷p3369

查看代码

查看代码

const int MAXN=1e5+5;
const double alpha=0.75,beta=0.5;
struct Node{
	int ch[2],v,cnt,tot;
	bool exist;
	void build(int v){
		this->v=v;
		ch[0]=ch[1]=0;
		cnt=tot=1;
		exist=true;
	}
}node[MAXN];
int pool[MAXN],pos1,temp[MAXN],pos2,root;
void Init(int n){
	for(int i=n;i>=1;i--)
	pool[++pos1]=i;
}
bool isbad(int pos){return alpha*node[pos].cnt<max(node[node[pos].ch[0]].cnt,node[node[pos].ch[1]].cnt)?true:false;}
bool isbad_2(int pos){return beta*node[pos].tot>node[pos].cnt?true:false;}
void dfs(int pos){
	if(!pos)return;
	dfs(node[pos].ch[0]);
	if(node[pos].exist)temp[++pos2]=pos;
	else pool[++pos1]=pos;
	dfs(node[pos].ch[1]);
}
void build(int lef,int rig,int &pos){
	if(lef>rig) return pos=0,void();
	int mid=lef+rig>>1;
	pos=temp[mid];
	if(lef==rig){
		node[pos].ch[0]=node[pos].ch[1]=0;
		node[pos].cnt=node[pos].tot=1;
		return;
	}
	build(lef,mid-1,node[pos].ch[0]);
	build(mid+1,rig,node[pos].ch[1]);
	node[pos].tot=node[pos].cnt=node[node[pos].ch[0]].cnt+node[node[pos].ch[1]].cnt+1;
}
void rebuild(int &pos){
	pos2=0;
	dfs(pos);
	build(1,pos2,pos);
}
void check(int &pos,int x){
	if(pos){
		if(isbad(pos)||isbad_2(pos))
		rebuild(pos);
		else if(node[pos].v<x)
		check(node[pos].ch[1],x);
		else
		check(node[pos].ch[0],x);
	}
}
int rank(int x){
	int pos=root,rk=1;
	while(pos){
		if(node[pos].v<x){
			rk+=node[node[pos].ch[0]].cnt+node[pos].exist;
			pos=node[pos].ch[1];
		}
		else
		pos=node[pos].ch[0];
	}
	return rk;
}
int kth(int rk){
	int pos=root;
	while(pos){
		if(node[node[pos].ch[0]].cnt+1==rk&&node[pos].exist)return node[pos].v;
		if(node[node[pos].ch[0]].cnt+node[pos].exist<rk){
			rk-=node[node[pos].ch[0]].cnt+node[pos].exist;
			pos=node[pos].ch[1];
		}
		else
		pos=node[pos].ch[0];
	}
}
void Insert(int &pos,int x){
	if(!pos){
		pos=pool[pos1--];
		node[pos].build(x);
		return;
	}
	node[pos].cnt++;node[pos].tot++;
	if(node[pos].v<x)Insert(node[pos].ch[1],x);
	else Insert(node[pos].ch[0],x);
}
void Insert(int x){
	Insert(root,x);
	check(root,x);
}
void Delate(int pos,int rk){
	node[pos].cnt--;
	if(node[node[pos].ch[0]].cnt+1==rk&&node[pos].exist){
		node[pos].exist=false;
		return;
	}
	if(node[node[pos].ch[0]].cnt+node[pos].exist<rk)Delate(node[pos].ch[1],rk-node[node[pos].ch[0]].cnt-node[pos].exist);
	else Delate(node[pos].ch[0],rk);
}
void Delate(int x){
	Delate(root,rank(x));
	check(root,x);
}
int main()
{
	int n=read_int(),opt,x;
	Init(MAXN-1);
	while(n--){
		opt=read_int(),x=read_int();
		switch(opt){
			case 1:
				Insert(x);
				break;
			case 2:
				Delate(x);
				break;
			case 3:
				enter(rank(x));
				break;
			case 4:
				enter(kth(x));
				break;
			case 5:
				enter(kth(rank(x)-1));
				break;
			case 6:
				enter(kth(rank(x+1)));
				break;
		}
	}
	return 0;
}
2020-2021/teams/legal_string/jxm2001/替罪羊树.1628601778.txt.gz · 最后更改: 2021/08/10 21:22 由 jxm2001