–一个集合支持快速插入、删除一个数字。
–支持快速查找一个数字在所有已插入数字中的排名。
–支持删除大小在某一个区间内的数字。
动态维护一个数列。可以在数列的任何位置插入删除,求区间和,Min,Max,进行区间翻转
•
这就需要用到二叉查找树( Binary SearchTree)。
•性质:
–这是一棵二叉树。
–对于任意一个节点,左子树的所有节点权值小于该节点权值,右子树所有节点权值大于该节点权值。
常见二叉查找树-------Treap
•Treap在基本的二叉搜索树基础上增加了一个『随机附加域』,整棵树不仅要满足二叉搜索树左儿子小右儿子大的性质,同时也要满足按照『随机附加域』成一个堆。正因为附加域的随机性,使得Treap可以保持一个较为随机的结构,其平均树高为O(logn)级别,这也是它能够实现基本操作时间复杂度O(logn)的原因。
Treap的存储
•通常来说我们用一个结构体来存储一个Treap节点:
•struct treap_node
•{
–int value,size,key;
–//value为点权值
–//key为随机附加域
–//size为子树的大小
–treap_node *ch[2];
–//记录左右儿子的指针
}
int tot=0;//记录当前内存池中哪些节点已经使用
获取一个新节点:
inlinetreap_node *get_new(int value){
–treap_node*now = pool + ++top;
–now->value =value;
–now->size =1;
–now->key = rand();
–now->ch[0] = now->ch[1] = null;
–returnnow;
}
•上面的代码里面出现了一个null,注意和空指针NULL区分。
•因为访问空指针会引起RE,为了避免麻烦,我们自己造一个假的空指针,这个指针null指向一个没有用的结构体。
•null的size=0,key=INF,这样根据堆的性质能保证它待在最底下而不会跑上来
Treap的旋转
wh=0;(左孩子)
treap_node *child = now->ch[wh];
now->ch[wh] = child->ch[wh ^ 1];
child->ch[wh ^ 1] = now;
now->update();
child->update();
•我们的Treap节点会维护一些和子树有关的信息,比如size(子树里面节点的个数)。当子树发生变化的时候,就需要更新这些信息:
void treap_node::update()
{
size=1+ch[0]->size+ch[1]->size;(本体+左孩子+右孩子)
}
•在实际应用中,update函数还有可能维护一些别的信息,比如子树里面节点权值的最大者,节点权值和等等。
•插入新节点的步骤:
–先获取一个新节点
–找到该新节点应该插入的位置。
–根据随机附加域进行调整,直到满足堆的性质(旋转)。
{
if (now==null)
{
now=get_new(value);
return;
}
if (now->value==value) return;
int wh=value < now->value ? 0 : 1;
insert(now->ch[wh], value);
now->update();
int minwh=
now->ch[0]->key < now->ch[1]->key ? 0:1;
if (now->ch[minwh]->key < now->key)
rotate(now,minwh);//旋转
}
Treap的删除
•如果一个节点是叶子节点,我们能够很方便的删除。
•所以我们的思路是,把要删除的点挪到叶子节点的位置。
•一路旋转,注意还要维护堆的性质。
void del(treap_node *&now, int value)
{
if (now==null) return;
if (now->value == value)
{
int minwh=
now->ch[0]->key < now->ch[1]->key ?0:1;
if (now->ch[minwh]!=null)
{
rotate(now,minwh);
del(now->ch[minwh^1],value);
now->update();
}
else now=null;
}
else
{
int wh=value < now->value ? 0 :1;
del(now->ch[wh],value);
now->update();
}
}
Treap的查找
{
if (now==null) return;
if (now->value == value)
{
int minwh=
now->ch[0]->key < now->ch[1]->key ?0:1;
if (now->ch[minwh]!=null)
{
rotate(now,minwh);
del(now->ch[minwh^1],value);
now->update();
}
else now=null;
}
else
{
int wh=value < now->value ? 0 :1;
del(now->ch[wh],value);
now->update();
}
}
•和insert,del函数中的查找方式一样。值小向左走,值大向右走。
•计算一个数字当前是第几小(大)的
•首先不停的查找这个数字。如果当前要向左走,答案不变;如果当前要向右走,答案要+左子树的大小+1。
int rank(treap_node *now,int value){
if (now==null) return 0;
int left_size = now->ch[0]->size;
if (now->value == value)
return left_size;
else if (value < now->value)
return rank(now->ch[0],value);
else
return left_size + 1 + rank(now->ch[1],value);
}
Treap找第k大
•这个过程和计算Rank正好相反。
•看一下k和当前左子树大小的关系:
–如果k<=ch[0]->size,往左走。
–如果ch[0]->size+1==k,bingo
–如果ch[0]->size+1<k,往右子树走,k-=ch[0]->size+1
int zd(treap_node *now,int k){
if (now==null) return INF;
int la=now->ch[0]->size + 1;
if (la==k)
return now->value;
else if (la<k)
return zd(now->ch[0],k);
else
return zd(now->ch[1],k-la);
}
•Treap相对于其它平衡树的优点是实现方便,且常数比较优良。
•缺点是功能不够强大。所以我们竞赛中通常采用splay而非treap。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
using namespace std;
const int INF = 1e9;
const int MAX_Q = 2e5 + 10;
struct treap_node
{
int value, size, key;
treap_node *ch[2];
void update()
{
size = ch[0]->size + ch[1]->size + 1;
}
} pool[MAX_Q], *root, *null;
int top = 0;
inline treap_node *get_new(int value)
{
treap_node *now = pool + ++top;
now->value = value;
now->size = 1;
now->key = rand() % INF;
now->ch[0] = now->ch[1] = null;
return now;
}
inline void rotate(treap_node *&now, int wh)
{
treap_node *child = now->ch[wh];
now->ch[wh] = child->ch[wh ^ 1];
child->ch[wh ^ 1] = now;
now->update();
child->update();
now = child;
}
void insert(treap_node *&now, int value) {
if (now == null) {
now = get_new(value);
return;
}
if (now->value == value)
return;
int wh = value < now->value ? 0 : 1;
insert(now->ch[wh], value);
now->update();
int minwh =
now->ch[0]->key < now->ch[1]->key ? 0 : 1;
if (now->ch[minwh]->key < now->key)
rotate(now, minwh);
}
void del(treap_node *&now, int value) {
if (now == null) return;
if (now->value == value) {
int minwh =
now->ch[0]->key < now->ch[1]->key ? 0 : 1;
if (now->ch[minwh] != null) {
rotate(now, minwh);
del(now->ch[minwh ^ 1], value);
now->update();
}
else
now = null;
}
else {
int wh = value < now->value ? 0 : 1;
del(now->ch[wh], value);
now->update();
}
}
inline int kth(treap_node *now, int k)
{
if (now == null) return INF;
int left_size = now->ch[0]->size;
if (k <= left_size)
return kth(now->ch[0], k);
else if (k == left_size + 1)
return now->value;
else
return kth(now->ch[1], k - left_size - 1);
}
int cou(treap_node *now, int value)
{
if (now == null) return 0;
int left_size = now->ch[0]->size;
if (now->value == value)
return left_size;
else if (value < now->value)
return cou(now->ch[0], value);
else
return left_size + 1 + cou(now->ch[1], value);
}
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()
{
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
srand(233);
null = pool;
null->value = 0;
null->size = 0;
null->key = INF;
null->ch[0] = null->ch[1] = null;
root = null;
int q = get_num();
while (q--)
{
char order;
while (!isalpha(order = getchar()));
int _ = get_num();
switch(order)
{
case 'I':
insert(root, _);
break;
case 'D':
del(root, _);
break;
case 'K':
int ans;
ans = kth(root, _);
if (ans == INF)
printf("invalid\n");
else
printf("%d\n", ans);
break;
case 'C':
printf("%d\n", cou(root, _));
break;
}
}
}