Loading

「学习笔记」treap(小根堆)

总结教训
对于 treap 使用小根堆性质,一定要特判左右子树是否存在,因为空节点的优先级为 \(0\),是最高的,不特判会出错我就这么错了,So
一定要特判!一定要特判!一定要特判!重要的事情说三遍
本文代码根据P3369 【模板】普通平衡树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)写的
模板,有注释:

//treap(小根堆性质) 
#include<bits/stdc++.h>
#define rint register int
typedef long long ll;
using namespace std;
inline ll read()//快读 
{
    ll x=0;
    bool fg=false;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')    fg=true;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return fg?~x+1:x;
}
const int N=1e6+5;
const ll INF=1e9+5;
int n,tot,root;//n 操作次数 tot 记录节点的标号 root 根节点 
int ch[N][2];//左右孩子 
ll val[N],pai[N],cnt[N],siz[N];
//val 数值 pai 存储优先级 cnt 该数值出现了几次 siz 大小 
inline void pushup(int id)//更新siz 
{
    siz[id]=siz[ch[id][0]]+siz[ch[id][1]]+cnt[id];
    //更新,该节点的大小=左子树的大小+右子树的大小+自己出现了几次 
}
int New(ll v)//插入新节点 
{
    val[++tot]=v;
    pai[tot]=rand();
    //随机函数,随即赋予一个数值,头文件<stdlib.h>
    cnt[tot]++;//cnt[tot]=1;
    siz[tot]++;//siz[tot]=1;
    return tot;
}
void spin(int &id,int d)
//旋转 id 要旋转的节点编号,因为会改变,所以要加&  d(direction) 旋转方向 
{
    int temp=ch[id][d^1];//记录与旋转方向相反的子节点,这里画图自己理解一下 
    ch[id][d^1]=ch[temp][d];//先存下来
    ch[temp][d]=id;//再修改 
    pushup(id);//先更新原节点 
    pushup(temp);//再更新旋转后的节点 
    id=temp;//最后更新id,以便其他操作需要 
}
void build()
//初始化,这两个点一个在最左下角,一个在最右下角,这里最左下角的会影响后面有关排名的的查询 
{
    root=New(-INF);
    ch[root][1]=New(INF);
    if(pai[root]>pai[ch[root][1]])    spin(root,0);//不符合小根堆性质,就左旋 
}
void insert(int &id,ll x)//插入节点 
{
    if(id==0)//如果已经到叶子节点了,直接插入新节点 
    {
        id=New(x);
        return;
    }
    if(x==val[id])    cnt[id]++;//如果和该节点数值相等,cnt(出现次数)+1 
    else
    {
        int d=x<val[id]?0:1;//判断是属于左子树还是右子树 
        insert(ch[id][d],x);
        if(pai[ch[id][d]]<pai[id])    spin(id,d^1);//要符合小根堆的性质 
    }
    pushup(id);//更新大小 
}
void del(int &id,ll x)
{
    if(!id)    return;//如果没有这个点,直接返回
    //这里else不能少,因为id是会改变的 
    if(x==val[id])//就是该节点,进行特判
    {
        if(cnt[id]>1)    cnt[id]--;//出现多次,减一即可 
        else//这里小根堆一定要进行特判子树,因为空节点的优先级最高,会报错 
        {
            if(ch[id][0]&&!ch[id][1])//只有左子树,没有右子树 
                spin(id,1),del(ch[id][1],x);
            else
            {
                if(!ch[id][0]&&ch[id][1])//只有右子树,没有左子树 
                spin(id,0),del(ch[id][0],x);
                else
                {
                    if(ch[id][0]&&ch[id][1])//左右子树都有 
                    {
                        int d=pai[ch[id][0]]<pai[ch[id][1]]?1:0;//判断优先级 
                        spin(id,d);//旋转 
                        del(ch[id][d],x);//删除 
                    }
                    else    id=0;//左右子树都没有 
                }
            }
        }                
    }
    else//如果不是该节点,向子树出发 
    {
        int d=x<val[id]?0:1;
        del(ch[id][d],x);
    }
    pushup(id);//更新大小 
}
ll get_rank(int id,ll x)//找x数(这里是数字)的排名(注意,是排名) 
{
    if(!id)    return 0;//如果没有这个数,返回0 
    if(x<val[id])    return get_rank(ch[id][0],x);
    //比当前节点小,向左子树中找 
    if(x>val[id])    return siz[ch[id][0]]+cnt[id]+get_rank(ch[id][1],x);
    //比当前节点大,向右子树中找 
    return siz[ch[id][0]]+1;
    //不大不小,则就是该节点,要返回这个点左子树的大小,并加上自己(也就是+1) 
}
ll get_val(int id,ll x)//找排名为x的数(注意,是数字) 
{
    if(!id)    return 0;//如果该节点为空,返回0 
    if(x<=siz[ch[id][0]])    return get_val(ch[id][0],x);
    //如果排名小于等于左子树的大小,就说明排名的这个位置在左子树中 
    if(x>siz[ch[id][0]]&&x<=siz[ch[id][0]]+cnt[id])    return val[id];
    //如果大于左子树大小,小于等于左子树大小+当前节点的大小,则就是该节点 
    return get_val(ch[id][1],x-siz[ch[id][0]]-cnt[id]);
    //以上都不符合,就是在右子树,这里x要减去左子树大小和当前节点的出现次数 
}
ll get_pre(ll x)//求前驱 
{
    int id=root;
    ll pre;
    while(id)
    {
        if(val[id]<x)//如果当前节点比x小,记录答案,去右子树中搜更优值 
        {
            pre=val[id];//记录答案 
            id=ch[id][1];//id转到右子树 
        }
        else    id=ch[id][0];//否则,在左子树中搜 
    }
    return pre;
}
ll get_nxt(ll x)//求后继 
{
    int id=root;
    ll nxt;
    while(id)
    {
        if(val[id]>x)//如果当前节点比x大,记录答案,去左子树中搜更优值 
        {
            nxt=val[id];//记录答案 
            id=ch[id][0];//id转到左子树 
        }
        else    id=ch[id][1];//否则,在右子树中搜 
    }
    return nxt;
}
int main()
{
    build();//初始化 
    n=read();//读入操作次数 
    for(rint i=1;i<=n;++i)
    {
        int op=read();
        ll x=read();
        switch(op)
        {
            case 1:
                insert(root,x);
                break;
            case 2:
                del(root,x);
                break;
            case 3:
                printf("%lld\n",get_rank(root,x)-1);
                break;
            case 4:
                printf("%lld\n",get_val(root,x+1));
                break;
            case 5:
                printf("%lld\n",get_pre(x));
                break;
            case 6:
                printf("%lld\n",get_nxt(x));
                break;
        }
    }
    return 0;
}

大根堆模板链接:treap(大根堆)模板

posted @ 2022-07-10 15:31  yi_fan0305  阅读(89)  评论(0编辑  收藏  举报