treap 简述

treap 的功能齐全, 插入删除,分裂合并……。

这里介绍一点基础操作, 以后会补更多操作。

以下键值均指满足二叉查找树性质的节点参数。

以下 rnd 均满足大根堆性质。

提交记录:普通平衡树 Luogu 数据


节点

struct node {
	int siz, val, rnd, c[2];
  // val 是键值, siz 是子树节点个数
  // rnd 是某个随机数, c[0/1] 是 左/右 子节点
	node() {}
	node(int s, int v) : siz(s), val(v) {
		rnd = rand();
		c[0] = c[1] = 0;
	}
} t[N];
int tot, root;

inline void upd (int me) {
	t[me].siz = t[t[me].c[0]].siz + t[t[me].c[1]].siz + 1;
}

一般来说初始化只会用到 size 和 val 两个参数。


旋转插入删除

旋转涉及三个指针, 因为把 x 的子节点悬上来, 需要改变之前 x 的父亲的指针, 关于这个, 如果在插入函数中使用传址参数, 节点就可以不用维护指向父亲的指针了。

删除就是把点悬到可以简便删除的地方, 复杂度与树高相关, 在删除的过程中需要注意维护 rnd 的堆性质。

treap 可以不 fatnode, 说人话就是 treap 里可以存在多个键值相同的点。

void ro (int & me, int dir) {
  int y = t[me].c[dir];
  t[me].c[dir] = t[y].c[dir ^ 1], t[y].c[dir ^ 1] = me;
  upd (me), upd (y);
  me = y;
}

void ins (int x, int & me) {
  if (! me) {
    t[me] = node (1, x);
    return ;
  }
  ++ t[me].siz;
  int dir = x > t[me].val;
  ins (x, t[me].c[dir]);
  if (t[t[me].c[dir]].rnd > t[me].rnd) ro (me, dir);
}

void del (int x, int & me) {
  if (! me) return ;
  if (t[me].val == x) {
    if (!t[me].c[0] || !t[me].c[1]) me = t[me].c[0] | t[me].c[1];
    else {
      int dir = t[t[me].c[1]].rnd > t[t[me].c[0]].rnd;
      ro (me, dir);
      -- t[me].siz;
      del (x, t[me].c[dir ^ 1]);
    }
  } else {
    -- t[me].siz;
    del (x, t[me].c[x > t[me].val]);
  }
}

rnk、kth 和前驱后继

这里 rnk(x) 定义为小于 x 的数的数量 + 1, kth(x) 定义为最大的 rnk ≤ x 的数。

x 的前驱 pre(x) 定义为最大的小于 x 的数, 后继 nxt(x) 则定义为最小的大于 x 的数。(都是严格的)

不过比较显然地,pre(x) = kth (rnk (x) - 1),nxt(x) = kth (rnk (x + 1)) 。

这里给出 rnk 和 kth 的实现(这里的代码, fid 就是 kth):

int fid (int x, int me) {
  int now = t[t[me].c[0]].siz + 1;
  if (x == now) return t[me].val;
  return x < now ? fid (x, t[me].c[0]) : fid (x - now, t[me].c[1]);
}

int rnk (int x, int me) {
  if (! me) return 1;
  return x > t[me].val ? t[t[me].c[0]].siz + 1 + rnk (x, t[me].c[1]) : rnk (x, t[me].c[0]);
}
posted @ 2021-03-06 22:33  xwmwr  阅读(119)  评论(0编辑  收藏  举报