treap

到开始写总结才搞明白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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值