到开始写总结才搞明白treap和BST的关系OTZ。。。
言归正传。
treap,也叫数堆
是指有一个随机附加域满足堆的性质的二叉搜索树。
treap∈BST,自然也是用于排序、搜索。
treap是一棵二叉树,并且是一棵排序二叉树。用于解决在集合中进行插入、删除、查询第K大等操作。
这玩意儿其实有点玄,人品不好一样完。
引入裸题:bzoj1503 [NOI2004]郁闷的出纳员
题目描述:
包括有n条语句和一个min值。
语句有4种:
1. I K 往集合里添加一个数K
2. A K 把集合里的数都加上K
3. S K 把集合里的数都减去K
4. F K 输出集合里第K大的数的值
当进行S操作后,集合里小于min的数都要退出集合。
排序二叉树:即任意一点左子树里的点的权值都比它小,右子树里的点的权值都比它大(反过来也行)。
但加入一个数时,从根开始,比当前节点小就左走,大就右走。走到叶子节点,加进去。
然而直接裸的建排序二叉树有可能会退化。
不难发现,不同的添加顺序会建成不同的树,所以二叉排序树有几率退化成一条链,大大减低搜索效率(就算不完全退化成链搜索效率通常也会很不理想)。
于是乎我们要引进优先级来优化,优先级是随机给的(rand()),优先级高的移到上面。这样就可以大大减低树退化的几率。
在加入一个数后判断它的优先级是否比它父亲节点大,大的话就要把它移上去。
至于怎么移?
这里就要用到一种操作:旋转。
效果:
具体代码如下:
//d=0时右旋(顺时针),d=1时左旋(逆时针)
void rotate(Node* &o,int d)
{
Node* k=o->kid[d];
o->kid[d]=k->kid[d^1];
k->kid[d^1]=o;
o=k;
o->kid[d^1]->maintion();
o->maintion();
}
这样就可以建好一棵树了。
那么如何实现名次树(询问第k大)?
再引入一个变量:size——表示该节点下接着多少节点(包括它自己)。
如上图(左)D的size为7,A的size为10。
随便推一下就知道每个节点的排名了。
代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
//#include<
using namespace std;
struct Node{
Node *kid[3];
int r,v,s;
int cmp(int x) const {
return x<v ? 0 : 1;
}
void maintion(){
s=1;
if(kid[1]!=NULL) s+=kid[1]->s;
if(kid[0]!=NULL) s+=kid[0]->s;
}
};
Node* root;
int N,Min,k,add,Size,sum;
char ch;
void rotate(Node* &o,int d)
{
Node* k=o->kid[d];
o->kid[d]=k->kid[d^1];
k->kid[d^1]=o;
o=k;
o->kid[d^1]->maintion();
o->maintion();
}
void insert(Node* &o,int x)
{
if(o==NULL) {o=new Node();o->kid[0]=o->kid[1]=NULL;o->v=x;o->r=rand();o->s=1;}
else {
int d=o->cmp(x);
insert(o->kid[d],x);
o->maintion();
if(o->kid[d]->r < o->r) rotate(o,d);
}
}
int dele(Node* &o)
{
if(o==NULL) return 0;
else if(o->v+add<Min){
if(o->kid[1]==NULL){
o=o->kid[0];
if(o!=NULL)
o->maintion();
return 1;
}
else if(o->kid[0]==NULL){
o=o->kid[1];
o->maintion();
return 1;
}
else {
int d=(o->kid[0]<o->kid[1]?0:1);
rotate(o,d);
int asd=dele(o->kid[d^1]);
o->maintion();
return asd;
}
}
else{
int asd= dele(o->kid[0]);
o->maintion();
return asd;
}
}
int kth(Node* &o)
{
int s=Size+(o->kid[1]==NULL ? 0 : o->kid[1]->s)+1;
if(s==k) return o->v;
else
{
if(s>k) return kth(o->kid[1]);
else{
Size=s;
return kth(o->kid[0]);
}
}
}
int main()
{
scanf("%d%d",&N,&Min);
while(N--)
{
scanf(" %c%d",&ch,&k);
if(ch=='I'){
if(k>=Min)
insert(root,k-add);
}
if(ch=='A'){
add+=k;
}
if(ch=='S'){
add-=k;
while(dele(root)) sum++;
}
if(ch=='F')
{
if(root==NULL) {
printf("-1\n");
continue;
}
if(root->s<k||k<0) {
printf("-1\n");
continue;
}
Size=0;
printf("%d\n",kth(root)+add);
}
}
printf("%d\n",sum);
return 0;
}