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(大根堆)模板 - yi_fan0305 - 博客园 (cnblogs.com)
标签:ch,return,int,ll,id,treap,小根堆,节点,模板 来源: https://www.cnblogs.com/yifan0305/p/16463234.html