[模板]普通平衡树-Treap版本
//Treap版本平衡树 #include<cstdio> #include<cstring> #include<string> #define WR WinterRain using namespace std; const int WR=1001000,INF=1e9+7; struct BalanceTree{ int son[2],fa;//son记录左右儿子 //0是左1是右 int val,sze,cnt,rnk; BalanceTree(){son[0]=son[1]=val=fa=sze=rnk=cnt=0;} }tree[WR]; int n; int root,tot; int read(){ int s=0,w=1; char ch=getchar(); while(ch>'9'||ch<'0'){ if(ch=='-') w=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ s=(s<<1)+(s<<3)+ch-48; ch=getchar(); } return s*w; } int add_point(int v){//新建一个节点 tree[++tot].val=v;//赋值 tree[tot].rnk=rand();//随机排名 tree[tot].sze=1;//大小为1 tree[tot].cnt=1;//当前节点对应数的个数(副本数)是1 //比如说有3个大小为1的数,1对应节点的cnt就为3 return tot; } void pushup(int k){ tree[k].sze=tree[tree[k].son[0]].sze+tree[tree[k].son[1]].sze+tree[k].cnt; //本节点子树大小=左儿子子树大小+右儿子子树大小+本节点副本数 } void build(){ root=add_point(-INF); tree[root].son[1]=add_point(INF);//先加入正无穷和负无穷,便于之后操作 //因为INF>-INF,所以INF是右子树 pushup(root); } void rotat(int &id,int dir){//dir表示方向,0是左旋1是右旋 int tmp=tree[id].son[dir^1];//旋转 tree[id].son[dir^1]=tree[tmp].son[dir]; tree[tmp].son[dir]=id; id=tmp; pushup(tree[id].son[dir]);//旋转以后size会改变,只更新自己和转上来的点 //pushup一下,注意先子节点再父节点 pushup(id);//旋转实质是在满足BST的性质的基础上比较优先级, //然后通过交换本节点和其某个叶子节点把链叉开成二叉形状(从而控制深度) } void insrt(int &id,int v){ if(!id){ id=add_point(v); return; } if(v==tree[id].val) tree[id].cnt++;//直接++ else{ int dir; if(v<tree[id].val) dir=0; else dir=1;//二叉搜索树,类似于二叉堆 insrt(tree[id].son[dir],v);//递归建树 if(tree[id].rnk<tree[tree[id].son[dir]].rnk) rotat(id,dir^1); //与左节点交换要右旋,右节点交换左旋 } pushup(id); } void remov(int &id,int v){//删除 if(!id) return;//发现这个节点本来就不存在 if(v==tree[id].val){ if(tree[id].cnt>1){ tree[id].cnt--; pushup(id); return;//若副本不止一个,减去一个就好 } if(tree[id].son[0]||tree[id].son[1]){ //发现只有一个值,且有儿子节点,我们只能把值旋转到底部删除 if(!tree[id].son[1]||tree[tree[id].son[0]].rnk>tree[tree[id].son[1]].rnk){ //当前点被移走之后,会有一个新的点补上来(左儿子或右儿子) //如果没有一边的儿子直接补另一边 //否则按照优先级,优先级大的补上来 rotat(id,1);remov(tree[id].son[1],v); //我们会发现,右旋是与左儿子交换,当前点变成右节点 }else{ rotat(id,0);remov(tree[id].son[0],v); //左旋则是与右儿子交换,当前点变为左节点 } pushup(id); return; } id=0;return;//发现这个节点是叶子节点,删掉完事 } if(v<tree[id].val) remov(tree[id].son[0],v); else remov(tree[id].son[1],v); pushup(id);//继续删除,最后更新 } int get_rank(int id,int v){ //printf("%d\n",111); if(!id) return 0; if(v==tree[id].val) return tree[tree[id].son[0]].sze+1; //查询到该值,由BST性质可知:该点左边值都比该点的值(查询值)小,故rank为左儿子大小+1 if(v<tree[id].val) return get_rank(tree[id].son[0],v); //如果小于,递归查询 else return get_rank(tree[id].son[1],v)+tree[id].cnt+tree[tree[id].son[0]].sze; //若查询值大于该点值,说明询问点在当前点的右侧,且此点左子树的值都小于查询值 //所以要加上cnt[id]和左子树大小 } int get_val(int id,int rk){ if(!id) return INF;//向右找找不到说明是正无穷 if(rk<=tree[tree[id].son[0]].sze) return get_val(tree[id].son[0],rk); //根节点排名已经大于rank了,说明rank对应的值在左儿子那里 else if(rk<=tree[id].cnt+tree[tree[id].son[0]].sze) return tree[id].val; //上一步排除了在左区间的情况,若是rank在左与中(目前节点)中,则直接返回目前节点(中区间)的值 else return get_val(tree[id].son[1],rk-tree[tree[id].son[0]].sze-tree[id].cnt); //剩下只能在右区间找了,rank减去左区间大小和根节点大小,继续递归 } int get_pre(int v){//找前驱 int id=root,pre; while(id){ if(tree[id].val<v) pre=tree[id].val,id=tree[id].son[1]; //满足当前节点比目标小,往当前节点的右侧寻找最大值(最小值最大) else id=tree[id].son[0]; //无论是比目标节点大还是等于目标节点,都不满足前驱条件,应往更小处靠近 } return pre; } int get_nxt(int v){//找后继 int id=root,nxt; while(id){ if(tree[id].val>v) nxt=tree[id].val,id=tree[id].son[0]; //同理,满足条件向左寻找最小值(最大值最小) else id=tree[id].son[1]; } return nxt; } int main(){ build(); n=read(); for(int i=1;i<=n;i++){ int opt=read(),x=read(); if(opt==1) insrt(root,x); if(opt==2) remov(root,x); if(opt==3) printf("%d\n",get_rank(root,x)-1); //注意:因为初始化时插入了INF和-INF,所以查询排名时要减1(-INF不是第一小,是“第零小”) if(opt==4) printf("%d\n",get_val(root,x+1)); if(opt==5) printf("%d\n",get_pre(x)); if(opt==6) printf("%d\n",get_nxt(x)); } return 0; }
本文来自博客园,作者:冬天的雨WR,转载请注明原文链接:https://www.cnblogs.com/WintersRain/p/16252645.html
为了一切不改变的理想,为了改变不理想的一切