【旋转】
平衡树中的旋转是指在不改变中序遍历的前提下改变树的形态的方式。(中序遍历=排名顺序)
右旋将当前点的左节点旋上来,左旋反之。(图侵删)
void rturn(int &k){ int o=t[k].l; t[k].l=t[o].r; t[o].r=k; up(k);up(o); k=o; }
原根k,新根o。
1.把k的左节点o解放出来并更新为o的右节点。
2.解放出来的o成为新根,其右孩子赋为k。
【Treap】树堆
功能:维护支持单点插入和单点删除的排名树。
特点:给每个节点随机堆权,在维持排名不变的前提下通过旋转维护堆结构,从而能使高度平衡。
Treap的左子树<根节点,右子树>根节点。
操作:
1.插入:递归找到适合插入的空位置定义新节点,回溯时维护堆性质。
2.删除:递归找到数字,然后通过比较左右孩子堆权向下旋转到底部删除。
3.查找
细节:
1.一定要时时维护堆性质,否则速度严重变慢。
2.记得srand。
例题和模版:【BZOJ】3224: Tyvj 1728 普通平衡树
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=100010; struct tree{int l,r,sz,rnd,num;}t[maxn*2]; int n,sz,root; void up(int k){t[k].sz=1+t[t[k].l].sz+t[t[k].r].sz;} void lturn(int &k){ int o=t[k].r; t[k].r=t[o].l; t[o].l=k; up(k);up(o); k=o; } void rturn(int &k){ int o=t[k].l; t[k].l=t[o].r; t[o].r=k; up(k);up(o); k=o; } void ins(int &k,int x){ if(!k){k=++sz;t[k].rnd=rand();t[k].num=x;t[k].sz=1;return;}//return t[k].sz++; if(x<=t[k].num){ ins(t[k].l,x); if(t[t[k].l].rnd<t[k].rnd)rturn(k);//turn } else{ ins(t[k].r,x); if(t[t[k].r].rnd<t[k].rnd)lturn(k); } } void del(int &k,int x){ //t[k].sz--; if(t[k].num==x){ if(t[k].l*t[k].r==0){k=t[k].l+t[k].r;return;} if(t[t[k].l].rnd<t[t[k].r].rnd){ rturn(k); t[k].sz--; del(t[k].r,x); } else{ lturn(k); t[k].sz--; del(t[k].l,x); } } else if(x<t[k].num)t[k].sz--,del(t[k].l,x);else t[k].sz--,del(t[k].r,x); } int find(int k,int x){ if(!k)return 0;//!k if(x<=t[k].num)return find(t[k].l,x); else return t[t[k].l].sz+1+find(t[k].r,x); } int rank(int k,int x){ if(t[t[k].l].sz+1==x)return t[k].num; if(x<t[t[k].l].sz+1)return rank(t[k].l,x); else return rank(t[k].r,x-t[t[k].l].sz-1); } int pre(int k,int x){ if(!k)return -1; if(t[k].num<x)return max(t[k].num,pre(t[k].r,x)); else return pre(t[k].l,x); } int suc(int k,int x){ if(!k)return 0x3f3f3f3f; if(t[k].num>x)return min(t[k].num,suc(t[k].l,x)); else return suc(t[k].r,x); } int main(){ srand(233);//srand!!!!!!!!!! scanf("%d",&n);sz=root=0; for(int i=1;i<=n;i++){ int opt,x; scanf("%d%d",&opt,&x); if(opt==1)ins(root,x); if(opt==2)del(root,x); if(opt==3)printf("%d\n",find(root,x)+1);//+1 if(opt==4)printf("%d\n",rank(root,x)); if(opt==5)printf("%d\n",pre(root,x)); if(opt==6)printf("%d\n",suc(root,x)); } return 0; }
【fhq-treap】可分裂合并的treap
真正的范浩强treap其实并不仅仅如此,这里使用这个名字只是为了方便。
功能:可分裂合并的treap,通过split和merge两个操作可以实现平衡树的所有功能(可以取代splay)。
(只有单点操作时仍然建议使用普通treap,fhq-treap步骤多所以常数大)
操作:
1.合并(merge):按照a左b右合并两棵平衡树,对两个树根比较堆权,将较小者放在上面后继续递归合并。
2.分裂(split):将平衡树按排名分裂成k和n-k两棵平衡树,通过判断当前根属于左树或右树后递归进行,传参得到最后的a和b。
3.查找区间:对于区间[a,b],将平衡树分裂成三部分,查询后合并。
4.线性建树:少用,做法是对最右一列维护栈,每次插入到右下角(栈顶),通过小型旋转维护堆性质并将栈顶相应弹出,最后记得t[0].r=0。
细节:
1.必须保证全过程不要影响到t[0]的值,t[0]的初始值也对全过程没有影响。
2.平衡树的上传必须考虑本身。
例题和模板:【BZOJ】1251: 序列终结者
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=100010; struct tree{int l,r,rnd,num,mx,add,delta,sz;}t[maxn*2]; int st[maxn]; int n,m,root; void down(int k){ if(t[k].delta){ swap(t[k].l,t[k].r); if(t[k].l)t[t[k].l].delta^=1; if(t[k].r)t[t[k].r].delta^=1; t[k].delta=0; } if(t[k].add){ int p=t[k].add; if(t[k].l)t[t[k].l].add+=p,t[t[k].l].mx+=p,t[t[k].l].num+=p; if(t[k].r)t[t[k].r].add+=p,t[t[k].r].mx+=p,t[t[k].r].num+=p;//keep 0->0!!! t[k].add=0; } } int max(int a,int b){return a<b?b:a;} void up(int k){//different from sgt! t[k].mx=max(t[k].num,max(t[t[k].l].mx,t[t[k].r].mx)); t[k].sz=1+t[t[k].l].sz+t[t[k].r].sz; } void dfs(int k){ if(!k)return; dfs(t[k].l); dfs(t[k].r); up(k); } void build(){ int top=0; for(int i=1;i<=n;i++){ t[i]=(tree){0,0,rand(),0,0,0,0,1}; while(top&&t[st[top]].rnd>t[i].rnd){ t[st[top]].r=t[i].l; t[i].l=st[top--]; } t[st[top]].r=i; st[++top]=i; } dfs(st[1]); t[0].r=0; t[0].mx=-0x3f3f3f3f;//make 0 no influence root=st[1]; } int merge(int a,int b){ if(!a||!b)return a^b; if(t[a].rnd<t[b].rnd){ down(a); t[a].r=merge(t[a].r,b); up(a); return a; } else{ down(b); t[b].l=merge(a,t[b].l); up(b); return b; } } void split(int k,int &l,int &r,int x){ if(!k)return void(l=r=0); down(k); if(x<t[t[k].l].sz+1){ r=k; split(t[k].l,l,t[k].l,x); } else{ l=k; split(t[k].r,t[k].r,r,x-t[t[k].l].sz-1); } up(k); } void plus(int l,int r,int x){ int a,b,c; split(root,b,c,r); split(b,a,b,l-1); t[b].add+=x;t[b].mx+=x;t[b].num+=x; root=merge(a,b);root=merge(root,c); } void turn(int l,int r){ int a,b,c; split(root,b,c,r);split(b,a,b,l-1); t[b].delta^=1; root=merge(a,b);root=merge(root,c); } int ask(int l,int r){ int a,b,c,ans; split(root,b,c,r);split(b,a,b,l-1); ans=t[b].mx; root=merge(a,b);root=merge(root,c); return ans; } int main(){ srand(233); scanf("%d%d",&n,&m); build(); for(int i=1;i<=m;i++){ int k,l,r,x; scanf("%d%d%d",&k,&l,&r); if(l>r)continue; if(k==1){ scanf("%d",&x); plus(l,r,x); } else if(k==2)turn(l,r); else printf("%d\n",ask(l,r)); } return 0; }