Loading

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

大根堆与小根堆性质相比简单很多,不用加特判
直接上代码:

//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])//有子树的情况 
            {
                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  阅读(84)  评论(0编辑  收藏  举报