平衡树
$Treap$ $and$ $Splay$
我隐约记得之前说NOIP之前不再学超纲算法。但是我又开始学了。所以说我NOIP会不会凉。这真是个好问题。中考前在家无聊地报了NOI的网上同步赛,后来发现要全网公示成绩......所以赶紧学点东西骗骗分。
记得以前初赛曾经考过一个东西叫做二叉排序树,这个东西非常好啊,平均时间复杂度是$O(logN)$的(如果插入节点的顺序随机)。但是构造数据就会被卡,树就成了一条链,退化到了O(N)。随机一下就可以不被卡啦,但是有时候要求强制在线,就用到了神奇的平衡树。
$Treap$依靠的是随机,给每个节点随机的权值,要求根节点的权值大于两棵子树,依赖这样的随机化保证均摊复杂度比较低,树高不超过$logN$。$Treap$=$Tree$+$Heap$;
这是我第一次用指针,其实指针还是挺有意思的,就是一定要注意不要访问空指针,否则程序就炸掉啦!
$Splay$依靠的是信仰,复杂度似乎也是有保证的,但是比较难证。
普通平衡树:https://www.lydsy.com/JudgeOnline/problem.php?id=3224
插入x数;
删除x数(若有多个相同的数,只删除一个);
查询x数的排名(若有多个相同的数,输出最小的排名);
查询排名为x的数;
求x的前驱(前驱定义为小于x,且最大的数);
求x的后继(后继定义为大于x,且最小的数);
这里讲一下旋转的问题:程序中的$rotate(n,d)$是指将$n$点的$d$儿子旋转上来作为新的$n$.以$d=1$为例子:
初始状态:
1 node *k=n->ch[d];
1 n->ch[d]=k->ch[d^1];
1 k->ch[d^1]=n;
显然这个样子转是不会破坏平衡性的~
1 # include <cstdio> 2 # include <iostream> 3 # include <cstring> 4 # include <string> 5 # include <algorithm> 6 # include <cmath> 7 # include <cstdlib> 8 # define R register int 9 # define ll long long 10 # define inf 100000008 11 12 using namespace std; 13 14 const int maxn=100005; 15 int n,opt,x; 16 struct node 17 { 18 int n,r,s,v; 19 node *ch[2]; 20 void in (int x) 21 { 22 n=s=1; 23 v=x; 24 r=rand(); 25 ch[0]=ch[1]=NULL; 26 } 27 void update () 28 { 29 s=n; 30 if(ch[0]) s+=ch[0]->s; 31 if(ch[1]) s+=ch[1]->s; 32 } 33 int cmp (int x) 34 { 35 if(x==v) return -1; 36 return x>v; 37 } 38 }*roo,pool[maxn]; 39 40 node *newnode () 41 { 42 static int cnt=0; 43 return &pool[++cnt]; 44 } 45 46 void rotate (node *&n,int d) 47 { 48 node *k=n->ch[d]; 49 n->ch[d]=k->ch[d^1]; 50 k->ch[d^1]=n; 51 n->update(); 52 k->update(); 53 n=k; 54 } 55 56 void ins (node *&n,int x) 57 { 58 if(!n) n=newnode(),n->in(x); 59 else 60 { 61 int d=n->cmp(x); 62 if(d==-1) n->n++; 63 else 64 { 65 ins(n->ch[d],x); 66 if(n->ch[d]->r > n->r) rotate(n,d); 67 } 68 n->update(); 69 } 70 } 71 72 void del (node *&n,int x) 73 { 74 if(!n) return; 75 int d=n->cmp(x); 76 if(d==-1) 77 { 78 if(n->n > 1) --n->n; 79 else if(!n->ch[1]) n=n->ch[0]; 80 else if(!n->ch[0]) n=n->ch[1]; 81 else 82 { 83 int f=(n->ch[0]->r > n->ch[1]->r)?0:1; 84 rotate(n,f); 85 del(n->ch[f^1],x); 86 } 87 88 }else del(n->ch[d],x); 89 if(n) n->update(); 90 } 91 92 int ask_key (node *&n,int x) 93 { 94 if(!n) return 1; 95 if(n->v==x) return (n->ch[0]?n->ch[0]->s:0)+1; 96 if(n->v<x) return (n->ch[0]?n->ch[0]->s:0)+n->n+ask_key(n->ch[1],x); 97 return ask_key(n->ch[0],x); 98 } 99 100 int ask_value (node *&n,int x) 101 { 102 if(!n||n->s<x||x<=0) return 0; 103 int s=n->ch[0]?n->ch[0]->s:0; 104 if(s<x&&x<=s+n->n) return n->v; 105 if(s>=x) return ask_value(n->ch[0],x); 106 return ask_value(n->ch[1],x-s-n->n); 107 } 108 109 int lef (node *&n,int x) 110 { 111 if(!n) return -inf; 112 if(n->v >= x) return lef(n->ch[0],x); 113 return max(n->v,lef(n->ch[1],x)); 114 } 115 116 int rig (node *&n,int x) 117 { 118 if(!n) return inf; 119 if (n->v <= x) return rig(n->ch[1], x); 120 return min(n->v,rig(n->ch[0], x)); 121 } 122 123 int main() 124 { 125 scanf("%d",&n); 126 for (R i=1;i<=n;++i) 127 { 128 scanf("%d%d",&opt,&x); 129 if(opt==1) ins(roo,x); 130 else if(opt==2) del(roo,x); 131 else if(opt==3) printf("%d ",ask_key(roo,x)); 132 else if(opt==4) printf("%d ",ask_value(roo,x)); 133 else if(opt==5) printf("%d ",lef(roo,x)); 134 else printf("%d ",rig(roo,x)); 135 } 136 return 0; 137 }
文艺平衡树:https://www.lydsy.com/JudgeOnline/problem.php?id=3223
区间翻转。这种题大概只能用$Splay$来做了吧,反正我不知道$Treap$怎么做这东西.
首先$Splay$也是一种平衡树,人气很高,常数据传比较大.它的复杂度不依赖于随机,但是也有一点玄学.它的核心操作就叫$Splay$,是一种很精巧的旋转,它的作用是将任意节点旋转到某一节点底下作为它的儿子,这样的特性就决定了她对于许多序列操作的不可替代性.关于$Splay$有几点要注意的问题:
·旋转不分左旋右旋($Treap$的关键在于将父亲往下转,因为有两个儿子所以要分开看;而$Splay$的核心是往父亲上面转,显然父亲只有一个);
·维护序列时可以打$delta$标记,但是一定要记得下放;
说了这么多下面就来讲讲旋转:
$Splay$的旋转很有意思,它基于一个相对简单的$rotate$操作(把自己转到父亲的位置),但是不是一味的瞎转,这样有可能虽然达成了目的却使链的长度越来越长;首先我们要找到这个节点的父亲,再找到他的爷爷,如果爷爷就是目标节点,转一下自己,退出.如果爷爷父亲自己三点共线,那么先转一下父亲,再转一下自己,否则自己转两次即可.
如果我们要操作区间$[l,r]$,那么首先找到$l$的前驱,把它转到根的位置,再将$r$的后继转到根的右儿子,此时根的右儿子的左儿子就是我们所需要的区间了,可以用来维护一些带插入的,线段树做不了的操作.
1 # include <cstdio> 2 # include <iostream> 3 # include <queue> 4 # include <cstring> 5 # include <string> 6 # define R register int 7 # define ll long long 8 9 using namespace std; 10 11 const int maxn=100005; 12 int n,m,x,y,roo; 13 struct node 14 { 15 int delta,ch[2],f,siz; 16 }t[maxn]; 17 18 int read(); 19 20 inline void update (int id) 21 { 22 t[id].siz=t[ t[id].ch[0] ].siz+t[ t[id].ch[1] ].siz+1; 23 } 24 25 inline void lin (int x,int f,int d) 26 { 27 t[x].f=f; 28 t[f].ch[d]=x; 29 } 30 31 int build (int l,int r) 32 { 33 if(l>r) return 0; 34 int mid=(l+r)>>1; 35 lin(build(l,mid-1),mid,0); 36 lin(build(mid+1,r),mid,1); 37 t[mid].delta=0; 38 update(mid); 39 return mid; 40 } 41 42 inline void pushdown (int n) 43 { 44 t[n].delta=0; 45 t[ t[n].ch[0] ].delta^=1; 46 t[ t[n].ch[1] ].delta^=1; 47 swap(t[n].ch[0],t[n].ch[1]); 48 } 49 50 void write (int x) 51 { 52 if(!x) return; 53 if(t[x].delta) pushdown(x); 54 write(t[x].ch[0]); 55 if(x!=1&&x!=n+2) printf("%d ",x-1); 56 write(t[x].ch[1]); 57 } 58 59 inline int D (int x) 60 { 61 return x==t[ t[x].f ].ch[1]; 62 } 63 64 void rotate (int x) 65 { 66 int f=t[x].f; 67 if(f==roo) roo=x; 68 int g=t[f].f; 69 int mf=D(x),gf=D(f); 70 int k=t[x].ch[mf^1]; 71 lin(k,f,mf),lin(f,x,mf^1),lin(x,g,gf); 72 update(f),update(x); 73 } 74 75 void splay (int x,int g) 76 { 77 while(t[x].f!=g) 78 { 79 if(t[ t[x].f ].f==g) rotate(x); 80 else if(D(x)==D(t[x].f)) rotate(t[x].f),rotate(x); 81 else rotate(x),rotate(x); 82 } 83 update(x); 84 } 85 86 inline int find (int x) 87 { 88 int no=roo; 89 x--; 90 if(t[no].delta) pushdown(no); 91 while (t[ t[no].ch[0] ].siz!=x) 92 { 93 if(t[ t[no].ch[0] ].siz<x) 94 x-=t[ t[no].ch[0] ].siz+1,no=t[no].ch[1]; 95 else no=t[no].ch[0]; 96 if(t[no].delta) pushdown(no); 97 } 98 return no; 99 } 100 101 int main() 102 { 103 n=read(),m=read(); 104 roo=build(1,n+2); 105 while(m--) 106 { 107 x=read(),y=read(); 108 x=find(x); 109 splay(x,0); 110 y=find(y+2); 111 splay(y,roo); 112 t[ t[y].ch[0] ].delta^=1; 113 } 114 write(roo); 115 return 0; 116 } 117 118 inline int read() 119 { 120 int x=0; 121 char c=getchar(); 122 while (!isdigit(c)) c=getchar(); 123 while (isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar(); 124 return x; 125 }