平衡树 二叉查找树 我们在OI中经常需要这样一种数据结构:
一个集合支持快速插入、删除一个数字。
支持快速查找一个数字在所有已插入数字中的排名。
支持删除大小在某一个区间内的数字。
动态维护一个数列。可以在数列的任何位置插入删除,求区间和,Min,Max,进行区间翻转。
Treap
SPLAY
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
using namespace std;
const int INF = 1e9;
const int MAX_Q = 1e5 + 10;
struct splay_node
{
int value, size, cnt;
splay_node *pre, *ch[2];
void update()
{
size = ch[0]->size + ch[1]->size + cnt;
}
int get_wh()
{
return pre->ch[0] == this ? 0 : 1;
}
void set_ch(int wh, splay_node *child);
} pool[MAX_Q], *root, *null;
void splay_node::set_ch(int wh, splay_node *child)
{
ch[wh] = child;
if (child != null) child->pre = this;
update();
}
int top = 0;
inline splay_node *get_new(int value)
{
splay_node *now = pool + ++top;
now->value = value;
now->size = 1;
now->cnt = 1;
now->pre = now->ch[0] = now->ch[1] = null;
return now;
}
inline void rotate(splay_node *&now)
{
splay_node *old_father = now->pre,
*grand = now->pre->pre;
int wh = now->get_wh();
old_father->set_ch(wh, now->ch[wh ^ 1]);
now->set_ch(wh ^ 1, old_father);
now->pre = grand;
if (grand != null)
grand->ch[grand->ch[0] == old_father ? 0 : 1] = now;
}
inline void splay(splay_node *now, splay_node *tar)
{
for (; now->pre != tar; rotate(now))
if (now->pre->pre != tar)
now->get_wh() == now->pre->get_wh() ?
rotate(now->pre) : rotate(now);
if (tar == null) root = now;
}
void insert(int value)
{
splay_node *last = null, *now = root;
splay_node *newone = get_new(value);
while (now != null)
{
last = now;
if (newone->value == now->value)
{
now->cnt++; now->size++;
splay(now, null);
return;
}
if (newone->value < now->value)
now = now->ch[0];
else
now = now->ch[1];
}
if (last == null)
root = newone;
else
{
if (newone->value < last->value)
last->set_ch(0, newone);
else
last->set_ch(1, newone);
splay(newone, null);
}
}
inline splay_node *find(int value)
{
splay_node *now = root;
while (now != null)
{
if (now->value == value)
break;
if (value < now->value)
now = now->ch[0];
else
now = now->ch[1];
}
if (now != null) splay(now, null);
return now;
}
inline int pre(int value)
{
int ans = -INF;
splay_node *now = root;
while (now != null)
{
if (now->value < value)
{
ans = max(ans, now->value);
now = now->ch[1];
} else
now = now->ch[0];
}
return ans;
}
inline int nxt(int value)
{
int ans = INF;
splay_node *now = root;
while (now != null)
{
if (now->value > value)
{
ans = min(ans, now->value);
now = now->ch[0];
} else
now = now->ch[1];
}
return ans;
}
void del(int value)
{
splay_node *now = find(value);
if (now == null) return;
if (now->cnt > 1)
{
now->cnt--;
now->size--;
return;
}
if (now->ch[0] == null && now->ch[1] == null)
root = null;
else if (now->ch[1] == null)
{
now->ch[0]->pre = null;
root = now->ch[0];
}
else if (now->ch[0] == null)
{
now->ch[1]->pre = null;
root = now->ch[1];
}
else
{
splay_node *_ = now->ch[0];
while (_->ch[1] != null) _ = _->ch[1];
splay(_, now);
_->set_ch(1, now->ch[1]);
_->pre = null;
root = _;
}
}
inline int get_rank(int value)
{
splay_node *now = root;
int left_size = 0;
while (now != null)
{
if (value == now->value)
{
int ans = left_size + now->ch[0]->size + 1;
splay(now, null);
return ans;
}
if (value < now->value)
now = now->ch[0];
else
left_size += now->ch[0]->size + now->cnt, now = now->ch[1];
}
return -1;
}
inline int kth(int k)
{
splay_node *now = root;
int left_size = 0;
while (now != null)
{
int _ = left_size + now->ch[0]->size;
if (_ + 1 <= k && k <= _ + now->cnt)
{
splay(now, null);
return now->value;
}
if (k <= _) now = now->ch[0];
else left_size = _ + now->cnt, now = now->ch[1];
}
return -1;
}
inline int get_num()
{
int num = 0;
char c;
bool flag = false;
while ((c = getchar()) == ' ' || c == '\n' || c == '\r');
if (c == '-') flag = true;
else num = c - '0';
while (isdigit(c = getchar()))
num = num * 10 + c - '0';
return (flag ? -1 : 1) * num;
}
int main()
{
null = pool;
null->value = 0;
null->size = 0;
null->cnt = 0;
null->pre = null->ch[0] = null->ch[1] = null;
root = null;
int q = get_num();
while (q--)
{
int order = get_num();
int _ = get_num();
switch(order)
{
case 1:
insert(_);
break;
case 2:
del(_);
break;
case 3:
printf("%d\n", get_rank(_));
break;
case 4:
printf("%d\n", kth(_));
break;
case 5:
printf("%d\n", pre(_));
break;
case 6:
printf("%d\n", nxt(_));
break;
}
}
}
Splay相比Treap的优势是能够更好的实现区间操作。
在进行区间操作之前我们通常要把我们要进行操作的区间弄到一棵子树上。
假如我们用Splay维护一个序列(也就是我们所谓的节点权值实际上是数列的下表,而数列里面数字本身的权值再开一个附加变量存储。
我们要对区间[L,R]进行操作,我们首先获得L-1节点和R+1节点。我们把L-1节点splay到根,再把R+1节点splay到根的右儿子位置。那么现在根的右儿子的左儿子这整棵子树就是我们要的区间。
因为我们在splay的过程中,一直会进行update,所以每个节点维护的附加信息(比如size,我们还可以加入sum,min,max等等)都一直是正确的。访问根节点右儿子的左儿子的信息,就可以直接获得区间信息。