模板及讲解
非旋转Treap:
Treap=Tree+heap,即键值$key$(满足排序二叉树性质(左小右大),并且按照中序遍历有序),附加值$val$(满足堆性质(孩子都比父节点小))
核心操作$O(nlogn)$:
- $newNode(x)$ 新建一个以$x$为权值的点
- $merge(a, b)$ 把$a$树和$b$树合并,必须满足$\forall key_a < \forall key_b$
- $split_v(a,v,x,y)$把$a$树按权值$v$分成$x,y$两棵树,一棵树小于等于$v$,一棵树大于$v$
- $split_k(a,v,x,y)$把$a$树按前$k$个分配成$x,y$两棵树
能够实现的操作$O(nlogn)$:
- $rank(x)$ 查询$x$的排名
- $kth(x)$ 查询第$x$大的数
- $pre(x)$ 查询$x$的前驱
- $succ(x)$ 查询$x$的后继
- $insert(x)$ 插入一个数权值为$x$
- $del(x)$ 删除一个权值为$x$的数
具体可以看相关代码
写时注意:
1 不要忘了srand(19260817)
2 $split$里$x, y$用*&
3 主程序记得加$initTreap()$
4 $merge$前面记得加赋值
可持久化Treap:
暂略
常见题型
1、直接使用操作完成
Q: 求第$k$大\求$x$的排名……
解: 直接套模板。
例题: bzoj3224
2、操作区间
Q: 翻转某个区间
解: 非旋Treap处理区间。
例题: bzoj3223
相关代码
bzoj3224非旋转Treap模板:包括基本操作
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define ms(i, j) memset(i, j, sizeof i)
#define LL long long
#define db double
using namespace std;
const int MAXN = 100000 + 5;
struct Treap *null, *pit, *root;//自建的null节点,内存池指针
struct Treap {
int key, val, s;//键值(满足排序二叉树性质),附加值(满足堆性质),以本节点为根的子树大小
Treap *lc, *rc;//Treap 左右结点
Treap() {};
void init(int key) {this->key = key, val = rand(), lc = rc = null, s = 1;}//初始化
void maintain() {s = lc->s + rc->s + 1;}//更新,维护以本节点为根的子树大小
}pool[MAXN];//内存池
Treap* newNode(int key) {
pit->init(key);
return pit++;
}
Treap* merge(Treap *a, Treap *b) {//合并两棵Treap,所有key(a)<key(b)才能合并,为了满足排序二叉树性质
if (a == null) return b;
if (b == null) return a;
if (a->val < b->val) {
a->rc = merge(a->rc, b), a->maintain();
//a小自然a要在b的上面,满足堆性质
return a;
}
if (a->val >= b->val) {
b->lc = merge(a, b->lc), b->maintain();
//a大自然a要在b的下面,满足堆性质
return b;
}
}
void split_v(Treap *o, int v, Treap *&x, Treap *&y) {//用权值分开一棵Treap,分开的第一棵根为x,第二棵根为y。x,y可以称为左右子树“下一个”位置,也就是如果还有满足左边的,就放在x或者y的位置
if (o == null) x = y = null; else {
if (o->key <= v) {
x = o, split_v(o->rc, v, o->rc, y);//x = o说明o左边全部放进x,递归右子树
} else y = o, split_v(o->lc, v, x, o->lc);
o->maintain();
}
}
void split_k(Treap *o, int k, Treap *&x, Treap *&y) {//按前k个分配分开一棵Treap,分开的第一棵根为x,第二棵根为y。x,y可以称为左右子树“下一个”位置,也就是如果还有满足左边的,就放在x或者y的位置
if (o == null) x = y = null; else {
if (k <= o->lc->s) {
y = o, split_k(o->lc, k, x, o->lc);//y = o说明o右边全部放进y,递归左子树
} else x = o, split_k(o->rc, k - o->lc->s - 1, o->rc, y);
o->maintain();
}
}
void insert(int v) {//插入一个数
Treap *a, *b;
split_v(root, v, a, b);
root = merge(merge(a, newNode(v)), b);
}
void del(int v) {//删除一个数
Treap *a, *b;
split_v(root, v, a, b);
Treap *c, *d;
split_v(a, v - 1, c, d);
d = merge(d->lc, d->rc);
a = merge(c, d), root = merge(a, b);
}
int kth(int k) {//查询排名为k的数
Treap *a, *b;
split_k(root, k - 1, a, b);
Treap *c, *d;
split_k(b, 1, c, d);
int ret = c->key;
b = merge(c, d), root = merge(a, b);
return ret;
}
int rk(int x) {//查询x的排名
Treap *a, *b;
split_v(root, x - 1, a, b);
int ret = a->s + 1;
root = merge(a, b);
return ret;
}
int pre(int x) {//求x的前驱
Treap *a, *b;
split_v(root, x - 1, a, b);
Treap *c, *d;
split_k(a, a->s - 1, c, d);
int ret = d->key;
a = merge(c, d), root = merge(a, b);
return ret;
}
int succ(int x) {//求x的后继
Treap *a, *b;
split_v(root, x, a, b);
Treap *c, *d;
split_k(b, 1, c, d);
int ret = c->key;
b = merge(c, d), root = merge(a, b);
return ret;
}
void initTreap() {
srand(19260827);//置随机数种子
pit = pool;//指针指向内存池
null = newNode(0), null->s = 0;//初始化自建的null节点
root = null;//初始化树根
}
void clean() {}
void solve() {
clean();
initTreap();
int Q;
scanf("%d", &Q);
while (Q--) {
int opt, x;
scanf("%d%d", &opt, &x);
switch (opt) {
case 1: insert(x); break;
case 2: del(x); break;
case 3: printf("%d\n", rk(x)); break;
case 4: printf("%d\n", kth(x)); break;
case 5: printf("%d\n", pre(x)); break;
case 6: printf("%d\n", succ(x)); break;
}
}
}
int main() {
solve();
return 0;
}