笔记:FHQ Treap 之三分裂

小知识点,但是好像没什么人写,所以写一篇。 在 NOIP 之前积攒一点 rp。

需要的知识

  • 平衡树(FHQ Treap)

前言

一般在写 FHQ Treap 的时候,都是按照某个值或排名 \(k\),将 Treap 分成小于等于和大于 \(k\) 的两棵树,我们将其称为二分裂。那么,所谓三分裂,就是将 Treap 按照小于、等于、大于 \(k\) 分裂成三棵树。

详解

在二分裂中,我们在分裂函数中传入两个引用 &L, &R,最后这棵树被分裂为分别以 L, R 为根的两棵树。现在我们传入三个引用 &L, &M, &R,当分裂函数执行完后,以 L, M, R 为根的三棵树分别包含了原先小于、等于、大于 \(k\) 的节点。

这样做有什么用呢?其实就是可以比较方便地得到等于 \(k\) 的节点的信息。其实二分裂也是可以做到的,叁分裂主要是种技巧,有时候用起来比较方便。 下面还是来看一道题。

例题

色球

题目描述

牛牛有 \(n\) 种颜色的彩色小球(编号 \(1\) 到 \(n\)),每种颜色的小球他都有无限多个。他还有 \(n\) 个球桶(编号 \(1\) 到 \(n\)),球桶的内径与小球直径相当且高度是无限大的,因此能容纳无限多的小球。他想用这些小球和球桶玩游戏。

一开始这些球桶都是空的,紧接着他会顺次地做 \(m\) 个操作,每个操作都是以下 \(3\) 类操作中的一个。

  1. \(push\:x\:y\:z\):把 \(x\) 个颜色为 \(y\) 的彩色小球放到第 \(z\) 个桶的最上面;
  2. \(pop\:x\:z\):把最上面的 \(x\) 个小球从第 \(z\) 个桶内拿出来;
  3. \(put\:u\:v\):把第 \(u\) 个桶的所有小球依次从顶部拿出放入第 \(v\) 个桶内。

现在他已经确定好了这 \(m\) 个操作,但在他开始玩之前,他想知道每次他进行第二类操作取出的最后一个小球是什么颜色。

输入格式

第一行两个正整数 。

接下来  行每行是一个操作:

  • 如果为第一类操作,则格式为 \(push\:x\:y\:z\)\(x,y,z\) 为正整数,\(1 \le x \le 10^9, 1 \le y,z \le n\));
  • 如果为第二类操作,则格式为 \(pop\:x\:z\)\(x, z\) 为正整数,\(1 \le x \le 10^9, 1 \le z \le n\))保证第 \(z\) 个桶内至少有 \(x\) 个小球;
  • 如果为第三类操作,则格式为 \(put\:u\:v\)\(u, v\) 为正整数,\(1 \le u,v \le n, u \not= v\)) 。

输出格式

对于每个第二类操作,输出牛牛取出的最后一个小球的颜色编号。

样例

  • Input
2 5
push 1 1 1
push 1 2 1
push 2 3 2
put 1 2
pop 2 2
  • Output
2

题解

感觉有点为了这碟醋包了一盘饺子。 主要就是展示一下三分裂的写法和用法。

std 给的链表做法,不过也可以用平衡树做,这里写 FHQ Treap 展示三分裂。

你会发现这题就是平衡树模板题。操作一二就是插入删除,操作三就是区间翻转。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll maxn = 200005;
ll n, q;
struct Treap {
	ll lson, rson;
	ll pri, tag;
	ll size, num, col;
	// num 表示一次 push 进来的球数,col 表示这次 push 进来的球的颜色
	// size 表示整个子树一共有多少球
} tree[maxn];
ll rootid[maxn], idcnt;

inline ll newNode(ll color, ll number) {
	++idcnt;
	tree[idcnt].lson = tree[idcnt].rson = 0;
	tree[idcnt].size = tree[idcnt].num = number;
	tree[idcnt].col = color;
	tree[idcnt].pri = rand();
	tree[idcnt].tag = 0;
	return idcnt;
}

inline void update(ll u) {
	tree[u].size = tree[u].num + tree[tree[u].lson].size + tree[tree[u].rson].size;
}

inline void pushdown(ll u) {
	if (!tree[u].tag) return;
	ll l = tree[u].lson, r = tree[u].rson;
	if (l) tree[l].tag ^= 1, swap(tree[l].lson, tree[l].rson);
	if (r) tree[r].tag ^= 1, swap(tree[r].lson, tree[r].rson);
	tree[u].tag ^= 1;
}

// 三分裂,按排名分裂
inline void split(ll u, ll k, ll &L, ll &M, ll &R) {
	if (!u) return L = M = R = 0, void();
	pushdown(u);
	// 注意我们是要取最后 k 个,因此 tree[tree[u].rson].size >= k 要向右子树递归。 
	if (tree[tree[u].rson].size >= k) {
		L = u;
		split(tree[u].rson, k, tree[u].rson, M, R);
	} else if (tree[tree[u].rson].size + tree[u].num < k) {
		// 如果右子树加上 u 本身的球数还不达 k 个,则向左子树递归。
		R = u;
		split(tree[u].lson, k - tree[tree[u].rson].size - tree[u].num, L, M, tree[u].lson);
	} else {
		// 如果恰好等于,则可以直接停止递归,将树分成 L,M,R 三棵。
		L = tree[u].lson, R = tree[u].rson, M = u;
		tree[u].lson = tree[u].rson = 0;
	}
	update(u);
}

ll merge(ll L, ll R) {
	if (!L || !R) return L + R;
	pushdown(L), pushdown(R);
	if (tree[L].pri > tree[R].pri) {
		tree[L].rson = merge(tree[L].rson, R);
		update(L);
		return L;
	} else {
		tree[R].lson = merge(L, tree[R].lson);
		update(R);
		return R;
	}
	return 0;
}

// 非常方便且快速地把我们需要的等于 k 的节点提出来了
// 而不用先分成两棵树,然后再单独提
inline ll delNode(ll u, ll k) {
	ll L = 0, M = 0, R = 0;
	split(rootid[u], k, L, M, R);
	// 删去最后 k 个,然而 tree[R].size + tree[M].num 可能是大于 k 个的。
	// 所以需要再建一个点把不该删除的部分给加回来,颜色为 M 的颜色,显然。
	rootid[u] = merge(L, newNode(tree[M].col, tree[R].size + tree[M].num - k));
	return tree[M].col;
}

signed main() {
	// freopen("color.in", "r", stdin);
	// freopen("color.out", "w", stdout);
	srand(time(NULL));
	scanf("%lld%lld", &n, &q);
	while (q--) {
		char opt[10];
		ll x, y, z, u, v;
		scanf("%s", opt);
		if (!strcmp(opt, "push")) {
			scanf("%lld%lld%lld", &x, &y, &z);
			// 直接新建一个点表示 x 个颜色为 y 的彩色小球,然后合并
			rootid[z] = merge(rootid[z], newNode(y, x));
		} else if (!strcmp(opt, "pop")) {
			scanf("%lld%lld", &x, &z);
			printf("%lld\n", delNode(z, x));
		} else {
			scanf("%lld%lld", &u, &v);
			// 基本区间翻转
			tree[rootid[u]].tag ^= 1;
			swap(tree[rootid[u]].lson, tree[rootid[u]].rson);
			rootid[v] = merge(rootid[v], rootid[u]);
			rootid[u] = 0;
		}
	}
	return 0;
}
posted @ 2024-11-27 20:52  jxyanglinus  阅读(31)  评论(0)    收藏  举报