首页 > 其他分享 >「学习笔记」重修 FHQ-treap

「学习笔记」重修 FHQ-treap

时间:2023-04-23 15:01:15浏览次数:47  
标签:merge int siz 重修 t2 t3 t1 treap FHQ

无旋 treap 的操作方式使得它天生支持维护序列、可持久化等特性。
无旋 treap 又称分裂合并 treap。它仅有两种核心操作,即为 分裂合并。通过这两种操作,在很多情况下可以比旋转 treap 更方便的实现别的操作。

变量与宏定义

#define ls ch[u][0]
#define rs ch[u][1]
int cnt, rt, top;
int ch[N][2], siz[N], val[N], pai[N], rub[N];

ls: 左孩子;
rs: 右孩子;
cnt: 计数器;
rt: 根;
top: “垃圾桶”的栈顶
ch: 左右孩子,0 为左孩子,1 为右孩子;
siz: 子树大小;
val: 键值;
pai:随机的值,用于堆排序;
rub: 垃圾桶。

操作

  • 按权值分裂

给定一个权值,小于等于该权值的分裂到左边,大于该权值的分裂到右边。
在分裂过程中也要维护二叉搜索树的性质,即当前节点左子树任意一个节点的 val 都小于等于当前节点的 val,右子树任意一个节点的 val 都大于当前节点的 val

void split1(int u, int c, int &x, int &y) { // 按照权值分裂
	if (u == 0) {
		x = y = 0;
		return ;
	}
	if (val[u] <= c) {
		x = u;
		split1(rs, c, rs, y);
	}
	else {
		y = u;
		split1(ls, c, x, ls);
	}
	pushup(u);
}

u: 当前节点;
c: 给定分裂的权值;
x: 要分裂成的左树;
y: 要分裂成的右树。

if (val[u] <= v) {
	x = u;
	split1(rs, c, rs, y);
}

如果当前节点的 val 小于 \(c\),那么久把当前节点连带它的左子树一起放到 x 上,接下来向右子树搜索,若还有要接到 x 上的节点,就接到 x 的右子树上,以此来维护二叉搜索树的性质。如下图(图片来自 \(\text{OI wiki}\))。
image
pushup(u); 为更新函数,后面会写。

  • 按子树大小分裂

将前 \(k\) 个节点分成一棵树,其他节点分成一棵树。

void split2(int u, int k, int &x, int &y) { // 按照子树大小分裂
	if (u == 0) {
		x = y = 0;
		return ;
	}
	pushup(u);
	if (siz[ls] + 1 <= k) {
		x = u;
		split2(rs, k - siz[ls] - 1, rs, y);
	}
	else {
		y = u;
		split2(ls, k, x, ls);
	}
	pushup(u);
}
if (siz[ls] + 1 <= k) {
	x = u;
	split2(rs, k - siz[ls] - 1, rs, y);
}

siz[ls] + 1 是当前的节点和它的左子树的总 sizsiz 小于 \(k\),放到左子树,x 变成 rs

  • 合并

两棵树合并,如果一棵树为空,则直接合并,否则,根据 pai 的大小来判断谁是父节点。
小根堆 pai 小的为父节点,大根堆 pai 大的为父节点。

int merge(int x, int y) {
	if (!x || !y) {
		return x + y;
	}
	if (pai[x] < pai[y]) {
		ch[x][1] = merge(ch[x][1], y);
		pushup(x);
		return x;
	}
	else {
		ch[y][0] = merge(x, ch[y][0]);
		pushup(y);
		return y;
	}
}

注意,合并时 x 是左树,y 是右树,一定要保证左右顺序!

  • 更新信息

void pushup(int u) {
	siz[u] = siz[ls] + siz[rs] + 1;
	return ;
}
  • 删除节点

这里是删除已知权值的点,若存在多个则只删一个,删除的点将放进“垃圾桶”,当有些节点要创建时先从“垃圾桶”里找可用的点,节省空间。

void del(int c) {
	int t1, t2, t3;
	split1(rt, c, t1, t2);
	split1(t1, c - 1, t1, t3);
	rub[++ top] = t3;
	t3 = merge(ch[t3][0], ch[t3][1]);
	rt = merge(merge(t1, t3), t2);
}
t3 = merge(ch[t3][0], ch[t3][1]);
rt = merge(merge(t1, t3), t2);

由于相同权值的节点只删了一个,t3 可能不为空,所以还要再合并起来.

  • 创建新节点

int New(int c) {
	int u;
	if (!top) {
		u = ++ cnt;
	}
	else {
		u = rub[top];
		top --;
	}
	val[u] = c;
	siz[u] = 1;
	pai[u] = rand();
	ls = 0, rs = 0;
	return u;
}
if (!top) {
	u = ++ cnt;
}
else {
	u = rub[top];
	top --;
}

如果“垃圾桶”里还有点,那就将这些点回收利用,否则就开新节点。

  • 插入

先将树按照权值分裂,然后将新节点依次与左树和右树合并,merge 的顺序不要搞反了。

void insert(int c) {
	int t1, t2;
	split1(rt, c, t1, t2);
	rt = merge(merge(t1, New(c)), t2);
}
  • 查询排名

按照权值分裂,返回 siz[ls] + 1

int ranking(int c) {
	int t1, t2, k;
	split1(rt, c - 1, t1, t2);
	k = siz[t1] + 1;
	rt = merge(t1, t2);
	return k;
}
  • 查询第 \(K\) 大

按照子树大小分裂即可

int K_th(int k) {
	int t1, t2, t3, c;
	split2(rt, k, t1, t2);
	split2(t1, k - 1, t1, t3);
	c = val[t3];
	rt = merge(merge(t1, t3), t2);
	return c;
}
  • 查找前驱

int pre(int c) {
	int t1, t2, t3, k;
	split1(rt, c - 1, t1, t2);
	split2(t1, siz[t1] - 1, t1, t3);
	k = val[t3];
	rt = merge(merge(t1, t3), t2);
	return k;
}
  • 查找后继

int nxt(int c) {
	int t1, t2, t3, k;
	split1(rt, c, t1, t2);
	split2(t2, 1, t2, t3);
	k = val[t2];
	rt = merge(t1, merge(t2, t3));
	return k;
}

由于 FHQ 的核心操作是分裂与合并,所以,不同于 treap,它可以方便的进行区间操作。

模板

struct FHQ {
	int cnt, rt, top;
	int ch[N][2], siz[N], val[N], pai[N], rub[N];

	void pushup(int u) {
		siz[u] = siz[ls] + siz[rs] + 1;
		return ;
	}
	
	int New(int c) {
		int u;
		if (!top) {
			u = ++ cnt;
		}
		else {
			u = rub[top];
			top --;
		}
		val[u] = c;
		siz[u] = 1;
		pai[u] = rand();
		ls = 0, rs = 0;
		return u;
	}
	
	void split1(int u, int c, int &x, int &y) { // 按照权值分裂
		if (u == 0) {
			x = y = 0;
			return ;
		}
		if (val[u] <= c) {
			x = u;
			split1(rs, c, rs, y);
		}
		else {
			y = u;
			split1(ls, c, x, ls);
		}
		pushup(u);
	}
	
	void split2(int u, int k, int &x, int &y) { // 按照子树大小分裂
		if (u == 0) {
			x = y = 0;
			return ;
		}
		pushup(u);
		if (siz[ls] + 1 <= k) {
			x = u;
			split2(rs, k - siz[ls] - 1, rs, y);
		}
		else {
			y = u;
			split2(ls, k, x, ls);
		}
		pushup(u);
	}
	
	int merge(int x, int y) {
		if (!x || !y) {
			return x + y;
		}
		if (pai[x] < pai[y]) {
			ch[x][1] = merge(ch[x][1], y);
			pushup(x);
			return x;
		}
		else {
			ch[y][0] = merge(x, ch[y][0]);
			pushup(y);
			return y;
		}
	}
	
	void insert(int c) {
		int t1, t2;
		split1(rt, c, t1, t2);
		rt = merge(merge(t1, New(c)), t2);
	}
	
	void del(int c) {
		int t1, t2, t3;
		split1(rt, c, t1, t2);
		split1(t1, c - 1, t1, t3);
		rub[++ top] = t3;
		t3 = merge(ch[t3][0], ch[t3][1]);
		rt = merge(merge(t1, t3), t2);
	}
	
	int ranking(int c) {
		int t1, t2, k;
		split1(rt, c - 1, t1, t2);
		k = siz[t1] + 1;
		rt = merge(t1, t2);
		return k;
	}
	
	int K_th(int k) {
		int t1, t2, t3, c;
		split2(rt, k, t1, t2);
		split2(t1, k - 1, t1, t3);
		c = val[t3];
		rt = merge(merge(t1, t3), t2);
		return c;
	}
	
	int pre(int c) {
		int t1, t2, t3, k;
		split1(rt, c - 1, t1, t2);
		split2(t1, siz[t1] - 1, t1, t3);
		k = val[t3];
		rt = merge(merge(t1, t3), t2);
		return k;
	}
	
	int nxt(int c) {
		int t1, t2, t3, k;
		split1(rt, c, t1, t2);
		split2(t2, 1, t2, t3);
		k = val[t2];
		rt = merge(t1, merge(t2, t3));
		return k;
	}
};

标签:merge,int,siz,重修,t2,t3,t1,treap,FHQ
From: https://www.cnblogs.com/yifan0305/p/17346031.html

相关文章

  • 「学习笔记」平衡树基础:Splay 和 Treap
    「学习笔记」平衡树基础:Splay和Treap点击查看目录目录「学习笔记」平衡树基础:Splay和Treap知识点平衡树概述Splay旋转操作Splay操作插入\(x\)查询排名为\(k\)......
  • 非旋treap
    非旋treap基本操作简述\(FHQ\)\(treap\)借用了\(treap\)的特点,基于\(split\)和\(merge\)操作实现,因其不用旋转,又叫非旋\(treap\),优点是好理解,上手快,代码短(ps:重点是......
  • treap
    \(treap\)基本操作简述\(treap\)将二叉搜索树和堆结合在一起为维护深度,给每个节点随机分配权值\(dat\),如下structtree{ints[2],vl,dt,ct,sz;}t[N];ilvoidupd(cs......
  • 【学习笔记】FHQ Treap
    \(\textbf{I.基本操作}\)\(\textbf{维护子树大小/size_up()}\)和线段树中的\(\text{push_up}\)操作类似,目的是维护以一个节点为根的子树的节点个数。inlinevoids......
  • FHQ-Treap 学习笔记
    FHQ-Treap学习笔记Treap=Tree+Heap.Treap是一种弱平衡二叉树,可以看作是笛卡尔树:其每个点有一个二元组\((Key,Value)\),\(Key\)满足二叉搜索树的性质,而\(Value\)......
  • fhq_treap
    FHQtreap参考:链接Treap:优点:码量小好写核心代码是复读机好理解支持操作多缺点:常数大了点维护平衡的奇怪操作:Treap:旋转FHQTreap:分裂合并节点信......
  • BZOJ 3224 普通平衡树 (BST+Treap)
    题目描述:您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:插入数值x。删除数值x(若有多个相同的数,应只删除一个)。查询数值x的排名(若有多个相同的数,应......
  • Treap 平衡二叉查找树
    【基本概念】Treap=Tree+Heap。Tree是指二叉搜索树,而Heap指的是二叉堆,一般是最小堆。Treap需要维护两值,一个是二叉搜索树中的键值(key),另一个是最小堆中的优先级(aux)。Treap是......
  • POJ2761 Feed the dogs(Treap)
    DescriptionWindlovesprettydogsverymuch,andshehasnpetdogs.SoJiajiahastofeedthedogseverydayforWind.JiajialovesWind,butnotthedogs,......
  • BZOJ1503 郁闷的出纳员 (Treap)
    DescriptionOIER公司是一家大型专业化软件公司,有着数以万计的员工。作为一名出纳员,我的任务之一便是统计每位员工的工资。这本来是一份不错的工作,但是令人郁闷的是,我们......