平衡树模板代码
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 }
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 }

浙公网安备 33010602011771号