FHQ-Treap

简介

基于分裂与合并的 Treap。

基本操作

split 分裂

按权值分裂:

inline void split(int p, int k, int &x, int &y) {// p 表示当前分裂树的根节点,x,y 表示分裂成的两棵树的根节点,k 为关键字
	if(! p) return x = y = 0, void();

	if(val[p] <= k) {
		x = p;
		split(rs[p], k, rs[p], y);
	}
	else {
		y = p;
		split(ls[p], k, x, ls[p]);
	}

	psup(p);
}

按排名分裂:

inline void split(int p, int k, int &x, int &y) {
	if(! p) return x = y = 0, void();

	if(sz[ls[p]] >= k) {
		y = p;
		split(ls[p], k, x, ls[p]);
	}
	else {
		x = p;
		split(rs[p], k - sz[ls[p]] - 1, rs[p], y);
	}

	psup(p);

	return ;
}

两种写法并非完全相同,需要看要求仔细斟酌。

其实就是跟权值相关还是跟编号相关的问题。

merge 合并

inline int merge(int x, int y) {
	if(! x || ! y) return x | y;

	if(rnd[x] > rnd[y]) { // 考虑 x,y 谁作为根,随机优先级大的作为根
		rs[x] = merge(rs[x], y); // x 为根,将 x 原先的右子树和以 y 为根的子树合并作为新的右子树
		psup(x);

		return x;
	}
	else  {
		ls[y] = merge(x, ls[y]); // 反之,将 y 原先的左子树和以 x 为根的子树合并作为新的左子树
		psup(y);

		return y;
	}
}
  • 注:根据笔者的写法,相当于是左儿子写右边,右儿子写左边的,不过无伤大雅。

扩展操作

insert 插入

inline void insert(int p) {
	int x = 0, y = 0;

	split(root, p - 1, x, y);
	merge(merge(x, create(p)), y);
}

相当于将新增节点当一颗新树合并进去。

delete 删除

inline void deleted(int p) {
	int x = 0, y = 0, z = 0;

	split(root, p, x, z);
	split(x, p - 1, x, y);

	y = merge(ls[y], rs[y]);
	root = merge(merge(x, y), z);

	return ;
}

分裂出只包含查询值 p 的平衡树,合并该树的左右儿子,则相当于丢掉根节点,等价于删去一个 p 元素。

一定要注意合并的顺序!要按先前分裂的顺序倒序合并啊。

get_rank 查询排名

inline int get_rank(int k) {
	int x = 0, y = 0, res = 0;

	split(root, k - 1, x, y);
	res = sz[x] + 1;
	root = merge(x, y);

	return res;
}

注意题目说明,用分裂后的信息去维护。

kthk

inline int kth(int k) {
	int p = root;

	while(true) {
		if(k <= sz[ls[p]]) p = ls[p];
		else if(k == sz[ls[p]] + 1) return val[p];
		else k -= sz[ls[p]] + 1, p = rs[p];
	}
}

发现和编号相关,注意用的是 sz[]

序列操作

P2042 [NOI2005] 维护数列 ,经典例题。

维护序列时 Treap 就不需要满足 BST 性质了。保证中序遍历为原序列即可。

add 建树

对于原题中的原序列的建树和插入一段序列,需要在此序列上新建一颗平衡树去维护。

效仿线段树中的递归建树,可以得到此代码:

inline int add(int L, int R) {
	if(L != R) {
		int mid = (L + R) >> 1;

		return merge(add(L, mid), add(mid + 1, R));
	}

	return create(a[L]);
}

相当与中序遍历维护上述性质。

懒标记

其实跟动态开点线段树差不多。

psup 标记上传

唯一可能和线段树有区别的地方了。

注意到平衡树的 BST 性质,根并不包含在左右儿子中,上传时算一下当前节点的贡献即可。

inline void psup(int p) {
	sz[p] = sz[ls[p]] + sz[rs[p]] + 1;
	sum[p] = sum[ls[p]] + sum[rs[p]] + val[p];
	pre[p] = max({0, pre[ls[p]], sum[ls[p]] + pre[rs[p]] + val[p]});
	suf[p] = max({0, suf[rs[p]], sum[rs[p]] + suf[ls[p]] + val[p]});
	res[p] = max(0, suf[ls[p]] + pre[rs[p]]) + val[p];
	if(ls[p]) res[p] = max(res[p], res[ls[p]]);
	if(rs[p]) res[p] = max(res[p], res[rs[p]]);

	return ;
}

各类修改函数

有区别吗?真的没有区别。

psd 下放标记

注意当前节点不存在时要 return

区间操作

把那一段区间分裂出来即可。

split(root, pos - 1, x, y);
split(y, tot, y, z);

注意合并顺序,按编号顺序合并即可。

posted @   end_switch  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示