平衡树模板代码

luoguP3369 普通平衡树

1.splay(88分,写得丑- - 9021AC了)

 

  1 #include <iostream> 
  2 #include <cstdio>
  3 #include <cstring>
  4 #include <algorithm>
  5 #define MN 400005
  6 #define rtf 400004 
  7 #define rt c[rtf][0] 
  8 using namespace std;
  9 int n,sz[MN],c[MN][2],tn,fa[MN],val[MN],sum[MN],cnt;
 10 inline int ri(){
 11     char c=getchar();int x=0,w=1;
 12     while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
 13     while( isdigit(c)){x=(x<<3)+(x<<1)+c-48;c=getchar();}
 14     return x*w;
 15 }
 16 inline void update(int x){//更新子树大小 
 17     sz[x]=sz[c[x][0]]+sz[c[x][1]]+sum[x];
 18     /*这个点带的儿子是左子树加右子树加上这个点的数量
 19     (可能一个值不只一个) */
 20 }
 21 inline void rotate(int x){//旋转 
 22     int f=fa[x],ff=fa[f],l=c[f][1]==x,r=l^1;
 23     /*f为x的父亲,ff为x的祖父,l判断x是y的哪一个儿子,
 24      异或:如果x是右儿子,l=1,r=1;如果x是左儿子,l=0,r=0^1=1; */ 
 25     fa[f]=x,fa[x]=ff,fa[c[x][r]]=f;//修改父亲: 
 26     //x成了x的父亲的父亲,x的祖父成了x的父亲,x的儿子的父亲修改为x之前的父亲
 27     c[ff][c[ff][1]==f]=x,c[f][l]=c[x][r],c[x][r]=f;//修改儿子: 
 28     //x的祖父的儿子变x,x的父亲的儿子变成x的儿子,x的儿子变为x的父亲(左右儿子通过异或解决了) 
 29     update(f);
 30 }
 31 inline void splay(int x,int y){//双旋 
 32     /*    一般我们都将一个节点旋转到根,但难判断;
 33         所以实现方法:将x旋到y的儿子
 34         LL和RR需要先旋转父节点,然后旋转当前结点;
 35         LR和RL需要两次旋转当前节点*/ 
 36     for(int f;fa[x]!=y;rotate(x))//如果x的父亲还不是y,还可再旋转;
 37         //只要祖父不是y ↓        //不论是哪一种情况,第二次旋转都是旋转当前结点 
 38         if(fa[f=fa[x]]!=y)rotate( (c[fa[f]][1]==f)^(c[f][1]==x) ? x : f );
 39                         //祖先的右儿子和父亲的右儿子不一致即为1,判断是否为RL/LR
 40         //Rl/LR直接旋转x,LL/RR先旋转父亲 
 41     update(x);//更新子树大小 
 42 }
 43 inline void ins(int f,int ty,int v){/*插入函数,
 44     c[f][ty]表示当前节点,ty=0表示当前节点是f的左儿子,否则为右儿子,v是要插入的权值 */
 45     if(!c[f][ty]){//如果节点为空可以插入 
 46         fa[c[f][ty]=++tn]=f;//[第几号节点入住f的儿子]的父亲为f 
 47         val[tn]=v;//第几号节点的值 
 48         sum[tn]=1;//这个val的数目++ 
 49         splay(tn,rtf);return;//把新儿子转到根,减复杂度 
 50     }
 51     if(v==val[c[f][ty]]){//如果正好碰到v一样的节点 
 52         sum[c[f][ty]]++;//这个节点的sum++ 
 53         splay(c[f][ty],rtf);return;//同样地,把这个点转到虚根 
 54     }
 55     ++sz[f=c[f][ty]];//= =f=c[f][ty]; ++sz[f]; 这个儿子的子树大小++; 
 56     ins(f,v>val[f],v);//如果v大于当前权值,递归右子树,否则左子树 
 57 }
 58 inline void del(int f,int ty,int v){//删除 
 59 //c[f][ty]表示当前节点,ty=0表示当前节点是f的左儿子,否则为右儿子,v是要插入的权值  
 60     if(val[c[f][ty]]==v){//如果遇到了这个值 
 61         int x=c[f][ty];
 62         if(sum[x]>1){sum[x]--,splay(x,rtf);return;}//这个点的sum>1减一即可 不用删除整个点 
 63         int tmp;//如果只有一个儿子↓ 直接把要删的这个点的儿子挪到这个点的位置,儿子上来,父亲更改 
 64         if(!(c[x][0]*c[x][1])){c[f][ty]=c[x][0]+c[x][1],fa[ c[x][ c[x][1]!=0 ] ]=f;return;}
 65         for(tmp=c[x][1];c[tmp][0]!=0;tmp=c[tmp][0]);//       x的儿子的父亲为x的父亲↑ 
 66         ////走到删除节点的右儿子,不停找这个节点的左儿子到左子树为空 
 67         splay(x,rtf),splay(tmp,rt);//先将删除的节点旋转到根,再把后继旋到根的右儿子 
 68         c[tmp][0]=c[x][0],rt=tmp,fa[c[x][0]]=tmp,fa[tmp]=rtf;
 69         //后继的左儿子改为要删除的那个数的左儿子,根的左儿子为这个后继,后继的父亲为根;
 70         update(tmp);return;
 71     }
 72     --sz[f=c[f][ty]];//= =f=c[f][ty]; --sz[f]; 这个儿子的子树大小--;
 73     del(f,v>val[f],v);//如果v大于当前权值,递归右子树,否则左子树 
 74 }
 75 inline int findk(int x,int k){//找第k小 
 76     if(k<=sz[c[x][0]])return findk(c[x][0],k);//如果左子树的大小>k,左子树递归  
 77     else if(k>sz[c[x][0]]&&k<=sz[c[x][0]]+sum[x]){splay(x,rtf);return val[x];}
 78     //如果左子树不够,但是当前节点加上去后够,返回当前节点 
 79     else return findk(c[x][1],k-=sz[c[x][0]]+sum[x]);//递归右子树,要查找的第几小可以减掉这些名次 
 80 }
 81 inline int getk(int x,int k){//查询k的排名 
 82     if(!x)return 0;
 83     if(val[x]>=k)return getk(c[x][0],k);//如果这个节点的值更大,找他的左子树 
 84     else return sz[c[x][0]]+sum[x]+getk(c[x][1],k); //如果找到了 答案=左子树大小+这个点个数+递归答案  
 85 }
 86 inline int pre(int x){//查找前驱 
 87     int tmp=getk(rt,x);//因为排名本要加一,这里没有加一求的就是前驱的排名 
 88     return findk(rt,tmp);//找排名为前驱的排名的那个数,就是前驱 
 89 }
 90 inline int suf(int x){//查找后继 
 91     int tmp=getk(rt,x+1)+1;// tmp为后继的排名,(x+1为后继)+1; 
 92     return findk(rt,tmp);//排名为后继的排名的那个数 就是后继 
 93 }
 94 int main(){
 95     n=ri();
 96     int opt,x;
 97     for(int i=1;i<=n;i++){
 98         opt=ri();x=ri();
 99         if(opt==1)ins(rtf,0,x);//插入x点 
100         else if(opt==2)del(rtf,0,x);//删除 
101         else if(opt==3)printf("%d\n",getk(rt,x)+1);//查询x的排名 +根这一个... 
102         else if(opt==4)printf("%d\n",findk(rt,x));//查询排名为x的 
103         else if(opt==5)printf("%d\n",pre(x));//前驱 
104         else if(opt==6)printf("%d\n",suf(x));//后继 
105     }
106 }

2.无旋treap

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <algorithm>
  4 #define MN 100005
  5 using namespace std;
  6 int n,val,opt,a[MN],root,x,y,z,tot;
  7 struct fhqtreap{
  8     int ls,rs,sz,val,rnd;//左儿子,右儿子,子树大小,值,随机数 
  9 }tr[MN];
 10 inline int ri(){
 11     char c=getchar();int x=0,w=1;
 12     while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
 13     while( isdigit(c)){x=(x<<3)+(x<<1)+c-48;c=getchar();}
 14     return x*w;
 15 } 
 16 inline int newnode(int x){//新建节点 
 17     tr[++tot].rnd=rand(),//随机化大法-- 
 18     tr[tot].val=x, tr[tot].sz=1;//这个点的v为x,子树大小1 
 19     return tot; 
 20 }
 21 inline void update(int now){//更新现在节点的子树大小
 22     tr[now].sz=tr[tr[now].ls].sz+tr[tr[now].rs].sz+1;//左右儿子子树大小和+自身 
 23 } 
 24 inline int merge(int x,int y){//将xy合并为一棵新树 最后返回当前树根
 25     if(!x||!y)return x+y;//空,为递归边界,返回当前树根
 26     if(tr[x].rnd<tr[y].rnd){//如下的ifelse只是随机化的力量佛系安排-- 
 27         tr[x].rs=merge(tr[x].rs,y);//把y接在x的右子树上
 28         update(x);return x;//合并完,更新,最终的根就是x -.- 
 29     }
 30     else{//因为递归的过程最终要保证x<y,在插入操作中,要么y在x的右下方,要么x在y的左下方 
 31         tr[y].ls = merge(x, tr[y].ls);//把x接在y的左子树上 
 32         update(y);return y;//合并完,更新,最终的根是y了 
 33     } 
 34 } 
 35 inline void split(int now, int k, int &x, int &y){//拆分?? 
 36 //权值小于等于k的分到左树(x),大于的分到右树(y)
 37 //整棵树变为两部分 now及其左子树,now的右子树
 38 
 39 /*整棵树变为两部分 now及其左子树,now的右子树,
 40 显然在上述情形下,下一次要划分的树就是now的右子树,
 41 并且按照二叉搜索树的性质应当把划分下来的,
 42 值仍然小于等于k的子树接在x的右子树上,所以此时now的右子树就是“x等待相接的位置”
 43 另一种情况同理
 44 */  if(!now){x=y=0;return ;} //不断递归的过程中now的右节点要么被更新,要么turn 0
 45     if(tr[now].val<=k)
 46         x=now, split(tr[now].rs, k, tr[now].rs, y);
 47     if(tr[now].val>k)
 48         y=now, split(tr[now].ls, k, x, tr[now].ls);
 49     update(now);//那些now=0的点不能update,会使本来sz为0的点变为1
 50 }
 51 
 52 /*ins操作: split(root,val,x,y); //按v拆分保障x<y
 53             root = merge(merge(x, new_node(val)), y);//ins y 
 54   del操作:下面的操作成功分出了一个c,
 55             而c的根的值一定为val,因为前面划分除出了一个a,
 56             a只有两种点,值为val的点和值<=val的点,c是从a中分出来的
 57             split(root, val, a, b);
 58             split(a, val-1, a, c);  
 59             c = merge(tree[c].lson, tree[c].rson);//
 60             root = merge(merge(a,c), b);//a,c最后拆,最先合 
 61    findk_rank(查找k的排名)操作:把整棵树以 val-1 split 分为x和y 
 62                         然后答案就是x.size+1,当然前提是这个值存在 
 63    getk_rank(获得排名为k的数)操作:
 64                         int kth(int now, int k) {
 65                             while(1) {
 66                                 int ls = tr[now].ls, rs = tr[now].rs;
 67                                 if(k <= tr[ls].siz) now = ls;    
 68                                 if(k == tr[ls].siz + 1) return now;
 69                                 else {k -= tr[ls].siz + 1;now = rs;}
 70                             }
 71                         } 
 72     val前驱查找操作:按val-1划分,左树里面找最大的,即在左树里面找rank为左树size的即可
 73     val后继查找操作:按val划分,右树rank为1的就是 
 74 */
 75 int kth(int now, int k) {
 76     while(1){
 77         int ls=tr[now].ls, rs=tr[now].rs;//记载左右儿子 
 78         if(k<=tr[ls].sz) now=ls;//如果这个排名小于子树,递归左子树 
 79         if(k==tr[ls].sz+1) return now; //正好是这个节点 
 80         if(k>tr[ls].sz+1) k-=tr[ls].sz+1, now=rs;//减少排名,改now,搜右子树 
 81     }
 82 }
 83 int main(){
 84     n=ri();
 85     while(n--){
 86         opt=ri(),val=ri();
 87         if(opt==1){
 88             split(root, val, x, y);//插入x 
 89             root = merge(merge(x, newnode(val)), y);
 90         } 
 91         if(opt==2){//删除x 
 92             split(root, val, x, y); 
 93             split(x, val-1, x, z);
 94             z = merge(tr[z].ls, tr[z].rs);
 95             root = merge(merge(x, z), y);
 96         } 
 97         if(opt==3){//查k的排名 
 98             split(root, val, x, y);
 99             split(x, val-1, x, z);
100             printf("%d\n", tr[x].sz+1);
101             merge(merge(x, z), y);
102         }
103         if(opt==4){printf("%d\n", tr[kth(root, val)].val);} //查排名为k的 
104         if(opt==5){//前驱 
105             split(root, val-1, x, y);
106             printf("%d\n", tr[kth(x, tr[x].sz)].val);
107             root = merge(x, y);
108         }
109         if(opt==6){//后继   
110             split(root, val, x, y);
111             printf("%d\n", tr[kth(y, 1)].val);
112             root = merge(x, y);
113         }
114     }
115 } 

luoguP3391 文艺平衡树

1.区间splay

 1 #include <iostream>
 2 #include <cstdio>
 3 #define R register
 4 #define MN 100005
 5 #define rtf 100004
 6 #define rt c[rtf][0]
 7 using namespace std;
 8 int c[MN][2],sz[MN],fa[MN],n,m;
 9 bool rev[MN];
10 inline int ri(){
11     char c=getchar();int x=0,w=1;
12     while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
13     while( isdigit(c)){x=(x<<3)+(x<<1)+c-48;c=getchar();}
14     return x*w;
15 }
16 inline void swap(int &x,int &y){x^=y,y^=x,x^=y;}//其实就是k=x,x=y,y=k.. 
17 inline void rever(int u){
18     swap(c[u][0],c[u][1]);//交换 
19     rev[c[u][0]]^=1,rev[c[u][1]]^=1,rev[u]^=1;//清除标记-- 
20 }
21 inline void update(int x){sz[x]=sz[c[x][0]]+sz[c[x][1]]+1;}
22 inline void rotate(int x){
23     int f=fa[x],ff=fa[f],l=c[f][1]==x,r=l^1;
24     fa[x]=ff,fa[f]=x,fa[c[x][r]]=f;
25     c[ff][c[ff][1]==f]=x,c[f][l]=c[x][r],c[x][r]=f; update(f);    
26 }
27 inline void splay(int x,int y){//普通splay正常操作-- 
28     for(int f;fa[x]!=y;rotate(x))
29         if(fa[f=fa[x]]!=y) rotate(c[fa[f]][1]==f^c[f][1]==x ? x : f);
30     update(x);
31 }
32 inline void ins(int f,int ty,int v){
33     if(!c[f][ty]){fa[c[f][ty]=v]=f;splay(v,rtf);return ;}
34     ++sz[f=c[f][ty]];//++子树大小 
35     ins(f,v>f,v);
36 }
37 inline void print(int u){
38     if(rev[u])rever(u);//被标记,翻转 
39     if(c[u][0])print(c[u][0]);//因为中序遍历左根右,所以递归根节点左子树到第一个数的位置
40     if(u>1&&u<n+2)printf("%d ",u-1);//因为有虚根,减一才是这个位置的数 
41     if(c[u][1])print(c[u][1]); //同理递归根节点的右子树
42 }
43 inline int findk(int u,int k){
44     if(rev[u])rever(u);//被标记,翻转 
45     if(k<=sz[c[u][0]])return findk(c[u][0],k);//比左子树小,找左子树 
46     if(k-=sz[c[u][0]]+1)return findk(c[u][1],k);//大.. 
47     return u;
48 }
49 int main(){
50     n=ri(),m=ri();int l,r;
51     for(R int i=1;i<=n+2;i++)ins(rtf,0,i);
52     for(R int i=1;i<=m;i++){
53         l=ri(),r=ri();
54         l=findk(rt,l),r=findk(rt,r+2);//查找x的前驱所在的位置,和y后驱所在的位置,因为预处理时ans存的是前趋,以直接查找x,而y的后驱变成了y+2
55         splay(l,rtf),splay(r,rt);//经过旋转后,此时根节点的右儿子的左子树就是需要翻转的区间,所以lazy标记
56         rev[c[c[rt][1]][0]]^=1;//经过旋转后,此时根节点的右儿子的左子树就是需要翻转的区间,所以lazy标记
57     }
58     print(rt);
59     return 0;
60 }

2.区间fhq treap

 1 #include <algorithm>
 2 #include <iostream>
 3 #include <cstring>
 4 #include <cstdio>
 5 #define MN 100005
 6 using namespace std;
 7 struct trnode{
 8     int val,lz,rnd,ls,rs,sz;
 9 }tr[MN];
10 int n,m,tot,root;
11 inline int ri(){
12     char c=getchar();int x=0,w=1;
13     while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
14     while( isdigit(c)){x=(x<<3)+(x<<1)+c-48;c=getchar();}
15     return x*w;
16 }
17 inline void update(int now) {//更新子树大小 
18     tr[now].sz = tr[tr[now].ls].sz + tr[tr[now].rs].sz + 1;
19 }
20 void down(int now) {//标记下传 交换 
21     tr[now].lz ^= 1;
22     swap(tr[now].ls, tr[now].rs);
23     tr[tr[now].ls].lz ^= 1;
24     tr[tr[now].rs].lz ^= 1;
25 }
26 inline int new_node(int val) {
27     tr[++tot].val = val;
28     tr[tot].sz = 1;
29     tr[tot].rnd = rand();
30     tr[tot].lz = 0; //虽然原来就是0,但是仍然建议写上,当有多次询问时,我们就可以不用memset,而是直接把tot置为0,但是这样就要初始化lz
31     return tot;
32 }
33 inline int build(int l, int r) { //建树1-n 
34     if(l > r) return 0;//结束 
35     int mid = l+r>>1;//求中间点 
36     int pos = new_node(mid);//中间点的根  
37     tr[pos].ls = build(l, mid-1);
38     tr[pos].rs = build(mid+1, r);
39     update(pos);
40     return pos;
41 }
42 int merge(int x, int y) { //将xy合并为一棵新树 最后返回当前树根
43     if(!x || !y) return x + y; //空,为递归边界,返回当前树根
44     if(tr[x].lz) down(x);//下推之前 要先检验是否有,因为用的是异或清理标记,不能随便下推了
45     if(tr[y].lz) down(y);
46     if(tr[x].rnd < tr[y].rnd) { //如下的ifelse只是随机化的力量佛系安排-- 
47         tr[x].rs = merge(tr[x].rs, y);//把y接在x的右子树上,原因如下else 
48         update(x);return x;//合并完,更新,最终的根就是x -.-  
49     } 
50     else {//因为递归的过程最终要保证x<y,在插入操作中,要么y在x的右下方,要么x在y的左下方 
51         tr[y].ls = merge(x, tr[y].ls);//把x接在y的左子树上
52         update(y);return y;//合并完,更新,最终的根是y了  
53     }
54 }
55 inline void split(int now, int k, int &x, int &y) { // 前k个值组成x,k+1到最后一个值组成y
56     /*解决区间问题的时候要按size分
57     这时k为size 若now树的左子树有>=k个节点,那么就从其左子树中找k个
58     否则,从其右子树中找k-tree[lson].size-1个(左边贡献了足够多的点) 
59     减去的1是根节点(有点像treap求第k大)
60     */ 
61     if(!now) x = y = 0;
62     else {
63         if(tr[now].lz) down(now);//标记下传 
64         if(k <= tr[tr[now].ls].sz) //画画图理解一下,k在now的左子树里就执行下面的语句
65             y = now, split(tr[now].ls, k, x, tr[now].ls); //now及其右树已分到y上,现在分now的左子树,并且y的等待位置是y的左子节点
66         else 
67             x = now, split(tr[now].rs, k-tr[tr[now].ls].sz-1, tr[now].rs, y);
68         update(now);
69     }
70 }
71 inline void rever(int l, int r) {
72     /*划分两次,把树分出来一个l~r的,然后进行翻转操作,
73     就是把一个点的左右儿子交换,对于翻转区间内每一个点都进行这个操作,
74     最后这个区间就被整体翻转了。这里有标记的思想,打上标记把树合并就行了
75     */
76     int a,b,c,d;
77     split(root, r, a, b);//前r个数字组成a,后r+1个数字组成b 
78     split(a, l-1, c, d);//a中找L-r组成d ,前L-1个数字组成c 
79     tr[d].lz ^= 1;
80     root = merge(merge(c, d), b);//合并1-r,合并1-r-r+1为根 
81 }
82 
83 inline void dfs(int now) {
84     if(tr[now].lz) down(now);//有标记就下传 
85     if(tr[now].ls) dfs(tr[now].ls);//有左儿子递归 
86     printf("%d ", tr[now].val);//中序遍历输出值 
87     if(tr[now].rs) dfs(tr[now].rs);//同上 
88 }
89 
90 int main() {
91     n=ri(),m=ri();int l,r;
92     root = build(1, n);
93     for(register int i=1; i<=m; i++)
94         l=ri(),r=ri(),rever(l, r);
95     dfs(root);
96     return 0;
97 }

 

 

posted @ 2018-12-24 21:05  flickerr  阅读(168)  评论(0)    收藏  举报