引言
这两天跟着网上dalao学了fhqTreap,感觉十分强大。它和Splay一样, 可以维护队列或排序二叉树, 但只需要两个操作: merge m e r g e 和 split s p l i t 就可以实现Splay所有功能, 而且不需要任何旋转! (博主蒟蒻每次写Splay都要调2h QAQ,不是RE就是TLE…)非旋Treap对于博主这样的手残党十分友好, 代码简短好记, 只是常数稍大。
下面博主来分别介绍非旋Treap的各种常见操作。(相信大家已经学过Treap了, 就不在这里废话)
我们需要的数组(可以开成结构体):
int tree[MX << 1][2]//左右儿子
, val[MX]//节点权值
, siz[MX]//子树大小
, pri[MX];//和Treap一样有优先级
题目传送门:排序二叉树 队列维护 可持久化平衡树
1. split
顾名思义, split是将一棵Treap分成两棵, 而分的方式有两种: 按节点的权值划分(用于维护二叉平衡树性质) 和按子树大小划分(即固定一棵分裂出的Treap的大小, 用于维护序列)。
首先我们需要两个节点
x
x
、, 作为分裂下的Treap的根节点,方便我们进行其他操作。为了统一,我们令大于划分值的节点都在以
y
y
为根的树上,小于划分值的节点都在以为根的子树上。然后我们从根节点开始向下, 若当前节点考虑的值小于划分考虑的值, 我们就可以将原Treap的右子树完全剖下, 作为
y
y
树的右子树, 然后使原指向节点的指针指向
y
y
节点的左子树。反之就将原Treap的左子树全部剖下, 作为树的左子树, 然后使原指向
x
x
节点的指针指向的右子树。
那么我们的y树剖出来会是这样的(相同颜色代表是原来Treap同一条链上的)
下面放出代码:
//维护排序二叉树版本
void split(int now, int tar, int &x, int &y)
{//tree[x][0]代表x的左儿子, tree[x][1]代表x的右儿子
if(!now) {x = y = 0; return;}//已经剖到最底层了
if(val[now] <= tar)
{x = now, split(tree[now][1], tar, tree[now][1], y);}
else {y = now, split(tree[now][0], tar, x, tree[now][0]);}
pushup(now);
}
//维护序列版本
void split(int now, int tar, int &x, int &y)
{
if(!now) {x = y = 0; return;}
pushdown(now);//有标记需下传
if (siz[tree[now][0]] >= tar) {y = now, split(tree[now][0], tar, x, tree[now][0]);}
else {x = now, split(tree[now][1], tar - 1 - siz[tree[now][0]], tree[now][1], y);}
pushup(now);
}
非常简单有木有…
2.merge
merge实际上是split的逆操作, 因为我们已经保证split的时候 y y 子树的权值都大于子树的权值, 所以我们现在需要维护Treap堆的性质。在这里我们不妨设其满足小根堆的性质,从两棵剖下的树的树根开始, 每次比较两棵树节点的随机的优先值, 如果 prix<priy p r i x < p r i y 我们就应将 x x 的左子树接到当前节点的左边, 再将的右子树继续与 y y 进行合并。反之我们就 将的右子树接到当前节点的右边, 将 x x 和的左子树继续合并。
代码如下:
int merge(int x, int y)
{
if(!x || !y) return x + y;//一棵树合并完成了, 返回另一棵树当前的指针值
if(pri[x] < pri[y]) {tree[x][1] = merge(tree[x][1], y); pushup(x); return x;}//此情况下等于合成后的树左儿子就不变了, 右儿子递归合成。
else {tree[y][0] = merge(x, tree[y][0]); pushup(y); return y;}
}
和左偏树的合并过程差不多, 也非常简洁。
3.第k大
因为我们维护了每个节点的siz信息, 直接像splay一样递归下去找就行了
int kth(int k, int now)
{
if(k == siz[tree[now][0]] + 1) return val[now];
else if(k <= siz[tree[now][0]]) return kth(k, tree[now][0]);
else return kth(k - siz[tree[now][0]] - 1, tree[now][1]);
}
4.查询k的排名
直接以 k−1 k − 1 为关键字分离子树, 排名即为 siz[x]+1 s i z [ x ] + 1
case 3:
{
split(root, b - 1, x, y);
printf("%d\n", siz[x] + 1);
root = merge(x, y);
break;
}
前驱后继
对于前驱, 将小于k部分的子树剖出来, 查询其最大的一个; 对于后继, 将大于k部分的子树剖出来, 查询其最小的一个。
case 5:
{
split(root, b - 1, x, y);
printf("%d\n", val[kth(siz[x], x)]);
root = merge(x, y);
break;
}
case 6:
{
split(root, b, x, y);
printf("%d\n", val[kth(1, y)]);
root = merge(x, y);
break;
}
接下来谈谈队列方面。
区间翻转问题。
我们直接将翻转区间分裂出来, 打上标记放回去就好了…比起Splay来不知道简单了多少倍…
IN void reverse(const int &lef, const int &rig)
{
split (root, rig + 1, a, b);
split (a, lef, c, d);
tag[d] ^= 1;
root = merge(merge(c, d), b);
}
可持久化也是fhq Treap的一大优势。 因为每次split 和 merge 的时候部分链的相同位置并没有改变, 这和主席树有很大相似之处(相比起来Splay每次伸展都会几乎完全打乱顺序), 所以我们可以仿照主席树的思路(博主很久很久之前写的), 对一部分节点进行复用, 就可以达到可持久化的目的。
代码相对于非持久化版本的fhq Treap有一点差异, 大家慢慢品味。
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define R register
#define W while
#define IN inline
#define gc getchar()
#define MX 500005
static bool fu;
template <class T>
IN void in(T &x)
{
fu = false; R char c = gc; x = 0;
W (!isdigit(c))
{if(c == '-') fu = true; c = gc;}
W (isdigit(c))
{x = (x << 1) + (x << 3), x += c - 48, c = gc;}
if(fu) x = -x;
}
namespace fhqTreap
{
struct Node
{
int son[2], val, siz, pri;
}tree[MX * 50];
int root[MX];
int cnt;
IN int randomm()
{
static int seed = 703;
return seed = int(seed * 48271LL % 2147483647);
}
IN void pushup(const int &now)
{tree[now].siz = tree[tree[now].son[0]].siz + tree[tree[now].son[1]].siz + 1;}
IN int new_node (const int &giv)
{
tree[++cnt].val = giv;
tree[cnt].siz = 1;
tree[cnt].pri = randomm();
return cnt;
}
int merge (int x, int y)
{
if(!x || !y) return x + y;
if(tree[x].pri > tree[y].pri)
{
int now = ++cnt; tree[now] = tree[x];
tree[now].son[1] = merge(tree[now].son[1], y);
pushup(now); return now;
}
else
{
int now = ++cnt; tree[now] = tree[y];
tree[now].son[0] = merge(x, tree[now].son[0]);
pushup(now); return now;
}
}
void split(int now, int k, int &x, int &y)
{
if(!now) {x = y = 0; return;}
if(tree[now].val <= k)
{
x = ++cnt; tree[x] = tree[now];
split(tree[x].son[1], k, tree[x].son[1], y);
pushup(x);
}
else
{
y = ++cnt; tree[y] = tree[now];
split(tree[y].son[0], k, x, tree[y].son[0]);
pushup(y);
}
}
void Del(int &root, int tar)
{
int x = 0, y = 0, z = 0;
split(root, tar, x, z);
split(x, tar - 1, x, y);
y = merge(tree[y].son[0], tree[y].son[1]);
root = merge(merge(x, y), z);
}
void insert(int &root, int tar)
{
int x = 0, y = 0, z = 0;
split(root, tar, x, y);
root = merge(merge(x, new_node(tar)), y);
}
int get_val(int now, int tar)
{
if(tar == tree[tree[now].son[0]].siz + 1) return tree[now].val;
else if(tar <= tree[tree[now].son[0]].siz) return get_val(tree[now].son[0], tar);
else return get_val(tree[now].son[1], tar - tree[tree[now].son[0]].siz - 1);
}
IN int kth(int &root, int tar)
{
int x, y;
split(root, tar - 1, x, y);
int ans = tree[x].siz + 1;
root = merge(x, y);
return ans;
}
IN int pre(int &root, int tar)
{
int x, y, k, ans;
split(root, tar - 1, x, y);
if(!x) return -2147483647;
k = tree[x].siz;
ans = get_val(x, k);
root = merge(x, y);
return ans;
}
IN int suc(int &root, int tar)
{
int x, y, ans;
split(root, tar, x, y);
if(!y) return 2147483647;
else ans = get_val(y, 1);
root = merge(x, y);
return ans;
}
}
using namespace fhqTreap;
int main(void)
{
int q;
R int base, com, t;
in(q);
for (R int i = 1; i <= q; ++i)
{
in(base), in(com), in(t);
root[i] = root[base];
switch(com)
{
case 1:
{insert(root[i], t); break;}
case 2:
{Del(root[i], t); break;}
case 3:
{printf("%d\n", kth(root[i], t)); break;}
case 4:
{printf("%d\n", get_val(root[i], t)); break;}
case 5:
{printf("%d\n", pre(root[i], t)); break;}
case 6:
{printf("%d\n", suc(root[i], t)); break;}
}
}
}