平衡树

前言

在此,我们总结一下平衡树的各种操作,可能不全,但会实时更新

Part.1 基础模板

这一部分是基本上不会变化的操作,也是每个平衡树的题目必写的模板。

No.1 旋转操作

void rotate(int x)
{
	int y = tr[x].fa;
	int z = tr[y].fa;
	int k = (tr[y].son[1] == x);
	
	tr[z].son[(tr[z].son[1] == y)] = x, tr[x].fa = z;
	tr[y].son[k] = tr[x].son[k ^ 1], tr[tr[x].son[k ^ 1]].fa = y;
	tr[x].son[k ^ 1] = y, tr[y].fa = x;
	
	return;
}

No.2 伸展操作

void splay(int x, int k)
{
	while (tr[x].fa != k)
	{
		int y = tr[x].fa;
		int z = tr[y].fa;
		
		if (z != k)
			if ((tr[y].son[1] == x) != (tr[z].son[1] == y))
				rotate(x);
			else rotate(y);
		rotate(x);
	}
	
	if (!k) root = x;
	
	return;
}

No.3 树的存储

其实这里具体题目会有具体的存储方式,但是对于每一道题目必须要存储的信息如下

struct warma
{
	int son[2], fa, val;
	void init(int _v, int _p)
	{
		val = _v;
		fa = _p;
	}
} tr[N];

可能有些题目需要子树大小,那么我们再将其加入一个变量,如下

struct warma
{
	int son[2], fa, val;
	int sz;
	
	void init(int v, int p)
	{
		val = v;
		fa = p;
		sz  = 1;
	}
} tr[N];

可能有些毒瘤题,维护的数据更多,如 P2042 [NOI2005] 维护数列 所需存储的信息如下

struct warma
{
	int son[2];
	int fa, val;
	bool rev, same;
	int sz, sum, ms, ls, rs;
	
	void init(int _v, int _p)
	{
		son[0] = son[1] = 0;
		fa = _p, val = _v;
		rev = same = 0;
		sz = 1;
		sum = ms = val;
		ls = rs = max(val, 0);
	}
} tr[N];

Part.2 操作模板

这一部分变化性就大了,不过其实你全背过也应该没问题

No. 1 加点操作

这个分为两种情况,如果你的平衡树里维护的值需要保证左儿子的值<=该节点的值<=右儿子的值的话,应该用如下方式加点

void add(int v)
{
	int u = root, p = 0;
	
	while (u)
	{
		p = u;
		u = tr[u].son[v > tr[u].val];
	}
	u = ++ idx;
	
	if (p) tr[p].son[v > tr[p].val] = u;
	tr[u].init(v, p);
	splay(u, 0);
	
	return;
}

如果你的平衡树只需要满足中序遍历顺序不变的话,那么应该这样加点(方式改编自P2596 [ZJOI2006]书架

void add(int v)
{
	int u = root;
	while (tr[u].son[1]) u = tr[u].son[1];
	tr[ ++ idx].init(v, u);
	tr[u].son[1] = idx;
	pos[v] = idx;
	splay(idx, 0);
	
	return;
}

No.2 删除操作

删除值为 x 的节点(x 已知)

void del(int x)
{
	int a = get_pre(x);
	int b = get_sub(x);
	splay(a, 0);
	splay(b, a);
	tr[b].son[0] = 0;
	return;
}

Part.3 询问操作

我们令性质 A 为:平衡树中满足左儿子的值<=该节点的值<=右儿子的值

No. 1 排名相关

满足 A,询问第 \(k\) 小的值的点的编号

该操作改编自P3224 [HNOI2012]永无乡

int get_k(int k)
{
	int u = root;
	
	while (u)
	{
		if (tr[tr[u].son[0]].sz >= k) u = tr[u].son[0];
		else if (tr[tr[u].son[0]].sz + 1 == k) return u;
		else
		{
			k -= tr[tr[u].son[0]].sz + 1;
			u = tr[u].son[1];
		}
	}
	
	return -1;
}

满足 A,询问排名为 k 的值为多少(k 已知)

int get_num_by_rank(int k)
{
	int u = root;
	while (u)
	{
		if (tr[tr[u].son[0]].sz >= k) u = tr[u].son[0];
		else if (tr[tr[u].son[0]].sz + 1 == k) return tr[u].val;
		else
		{
			k -= tr[tr[u].son[0]].sz + 1;
			u = tr[u].son[1];
		}
	}
	return -1;
}

No. 2 值->点的编号

void fd(int v) //查找值为x的点
{
	int u = root;
	while (tr[u].val != v)
	{
		if (tr[u].val > v)
		{
			if (!tr[u].son[0]) break;
			u = tr[u].son[0];
		}
		else
		{
			if (!tr[u].son[1]) break;
			u = tr[u].son[1];
		}
	}
	splay(u, 0);
}

No.3 前驱后继

1. 满足 A,查找值为 v 的点的前驱节点的编号(v 已知且存在)

int get_pre(int v)
{
	fd(v);
	if (tr[root].val < v) return root;
	
	int u = tr[root].son[0];
	while (tr[u].son[1])
		u = tr[u].son[1];
	
	return u;
}

2. 满足 A,查找值为 v 的点的后继节点的编号(v 已知且存在)

int get_sub(int v)
{
	fd(v);
	if (tr[root].val > v) return root;
	
	int u = tr[root].son[1];
	while (tr[u].son[0])
		u = tr[u].son[0];
	
	return u;
}

3. 满足 A,找到小于 v 的最大数(v 已知但可以不存在)

int get_pre(int v) //找小于v的最大数
{
	int u = root;
	int res = -INF;
	
	while (u)
	{
		if (tr[u].val < v)
		{
			res = max(res, tr[u].val);
			u = tr[u].son[1];
		}
		else u = tr[u].son[0];
	}
	
	return res;
}

4. 满足 A,找到大于 v 的最小数(v 已知但可以不存在)

int get_suc(int v) //找大于v的最小数
{
	int u = root;
	int res = INF;
	
	while (u)
	{
		if (tr[u].val > v)
		{
			res = min(res, tr[u].val);
			u = tr[u].son[0];
		}
		else u = tr[u].son[1];
	}
	
	return res;
}
posted @ 2022-08-31 21:14  LittleMoMol  阅读(49)  评论(0)    收藏  举报
*/