平衡树学习笔记

平衡树学习笔记

一.BST:

Binary Search Tree,二叉搜索树。

用来维护满足某一大小关系的序列。

可实现操作如下:

1.插入x数
2.删除x数
3.查询x数的排名
4.查询排名为x的数
5.求x的前驱
6.求x的后继

对于每一种操作,最多只会从树根到叶子(后面会详细讲解),时间复杂度最大是树高。

满足以下性质:

对于树上任意一点x,左子树中所有点的权值小于x的权值,右子树中所有点的权值大于x的权值

不难发现,对于同一种大小关系,此树有多种结构。

如图:

 

所以,我们为什么不调整结构,使树高最小(即最好变成完全二叉树,使时间复杂度达到稳定的O(㏒2n)呢?)

1.平衡树核心操作:旋转:(当然,BST不能,因为没有东西判断什么时候旋转)

 

即为代码:

1 inline void pushup(int x){siz[x]=siz[son[x][0]]+siz[son[x][1]]+num[x];}
1 inline void rotate(int x)
2 {
3     int y=f[x]; int z=f[y],k=(son[y][1]==x);
4     if(z) son[z][son[z][1]==y]=x; f[x]=z;
5     son[y][k]=son[x][k^1],f[son[y][k]]=y;
6     son[x][k^1]=y,f[y]=x;
7     pushup(y),pushup(x);
8 }

2.插入:

对于任一节点x,插入的值为u,

则若x的权值小于u,u应该在x的右子树,x变为x的右儿子;

若x的权值大于u,u应该在x的左子树,x变为x的左儿子;

最后当x==0时,新建一个点。

//下面代码中:w[x]是权值,siz[x]是子树大小,num[x]是权值都为w[x]的值有几个,son[x][0]:左儿子,son[x][1]:右儿子,f[x]:父亲。

1 void ins(int x,int u)
2 {
3     if(!x){++o,w[1]=u,siz[1]=num[1]=1,f[1]=0,root=1; return;}
4     int t=son[x][w[x]<u];
5     while(w[x]!=u&&t) ++siz[x],x=t,t=son[x][w[x]<u];
6     if(w[x]!=u) ++siz[x],++o,w[o]=u,siz[o]=num[o]=1,son[x][w[x]<u]=o,f[o]=x;
7     else ++num[x];
8 }

3.删除:

首先找到应被删除的点x,

当x的左儿子或右儿子为空时,直接让他们顶替x的位置。

如果x的左、右儿子都在时,怎么办呢?

将x旋到左儿子或右儿子为空时就行了。

 1 void del(int x,int y){
 2     x=root;//root:根 
 3     while(w[x]!=y&&x) --siz[x],x=son[x][w[x]<y];
 4     if(!x) return -1;
 5     if(num[x]>1) --num[x],--siz[x];
 6     else{
 7         while(son[x][1]&&son[x][0]) rotate(son[x][0]),--siz[f[x]];
 8         son[f[x]][son[f[x]][1]==x]=(son[x][0]|son[x][1]); f[son[x][0]|son[x][1]]=f[x];
 9     }
10 }
11 //临时手打,有错勿怪 
12 //丑陋码风,不喜勿喷 

4.排名:

找到权值为y的x,在找的过程中求出小于y的数sum,输出siz[son[x][0]]+sum+1。

1 void rnk(int x,int y){
2     x=root; int sum=0;
3     while(w[x]!=y&&x){
4         if(w[x]<y) sum+=siz[son[x][0]]+num[x],x=son[x][1];
5         else x=son[x][0];
6     }
7     if(!x) return -1;
8     return siz[son[x][0]]+1+sum;
9 }

5.k大数:

像3一样,当siz[son[x][0]]<y-sum<=siz[son[x][0]]+num[x]时输出w[x]。

 1 int kth(int x,int y)
 2 {
 3     int t;
 4     while(1)
 5     {
 6         t=siz[son[x][0]]+num[x];
 7         if(y>t) y-=t,x=son[x][1];
 8         else if(y>siz[son[x][0]]&&y<=t) return w[x];
 9         else x=son[x][0];
10     }
11 }

6.前缀:

找到权值为y的x,在找的过程中求出小于y的数最大值,返回x左子树中最大值与路上最大值的最大值。(都快把自己绕晕了)

 1 void pre(int x,int y){
 2     int maxn=-1;
 3     while(w[x]!=y&&x){
 4         if(w[x]<y) maxn=max(w[x],maxn),x=son[x][1];
 5         else x=son[x][0];
 6     }
 7     while(x) x=son[x][0],maxn=max(maxn,w[x]);
 8     return maxn;
 9 }
10 //临时手打,有错勿怪 
11 //丑陋码风,不喜勿喷 

7.后缀:

找到权值为y的x,在找的过程中求出大于y的数最小值,返回x左子树中最小值与路上最小值的最小值。

 1 void nxt(int x,int y){
 2     int minw=inf;
 3     while(w[x]!=y&&x){
 4         if(w[x]>y) minw=min(w[x],minw),x=son[x][0];
 5         else x=son[x][1];
 6     }
 7     while(x) x=son[x][1],minw=min(minw,w[x]);
 8     return minw;
 9 }
10 //临时手打,有错勿怪 
11 //丑陋码风,不喜勿喷 

BST中代码只是临时手打,帮助大家理解,最好不要仿照,如有错误请指出,非常感谢。

不过接下来代码是测过的。

二.Treap

1.介绍:

Treap=tree+heap,是一种满足BST和堆性质的数据结构。

其用递归和‘&’减少了很多码量,代码简洁,常数小,是写普通平衡树是最好打的数据结构。

缺点:也只能维护普通平衡树的操作,对于可持久化和翻转、合并、分离等操作无能为力。

2.特点:

用rnd数组储存随机值,用随机来满足树的平衡。

父亲rnd值始终比儿子的(大或小自己定义,我这里是按大)。

3.变化:(较BST)

多用'&'和递归,不需要父亲。

<1>.rotate

1 void size(int u){siz[u]=siz[son[u][0]]+siz[son[u][1]]+1;}
2 void rotate(int &u,int d){//son[u][d^1] d==0?左旋:右旋。 
3     int p=son[u][d^1];
4     son[u][d^1]=son[p][d];
5     son[p][d]=u;
6     size(u),size(p);//重新统计,先儿子后父亲 
7     u=p;//实际上是将p顶替u的位置 
8 }

<2>.ins

 1 int cmp(int u,int x){if(w[u]==x) return -1; return w[u]<x;}
 2 //判断是左儿子还是右儿子 
 3 void ins(int &u,int x)
 4 {
 5     if(!u){u=++cnt; w[u]=x; rnd[u]=rand()%inf; siz[u]=1;}//新建点 
 6     else{
 7         ++siz[u];
 8         int d=cmp(u,x); if(d==-1) d=0;
 9         ins(son[u][d],x);
10         if(rnd[u]<rnd[son[u][d]]) rotate(u,d^1);
11         //插入后如果不满足堆的性质,将插入的数旋上来 
12     }
13 }

 <3>.del:

 1 void del(int &u,int x){
 2     int d=cmp(u,x);
 3     if(d==-1){
 4         if(!(son[u][0]*son[u][1])){u=son[u][0]+son[u][1]; return;}//左或右儿子为空 
 5         else{
 6             int d2=(rnd[son[u][0]]>rnd[son[u][1]]?1:0);
 7             rotate(u,d2); del(son[u][d2],x);
 8             //将rnd大的儿子放在父亲处 
 9         }
10     }
11     else del(son[u][d],x);
12     size(u);
13 }

<4>.rnk ,<5>.kth ,<6>.pre和<7>.nxt可以沿用BST的,只不过,这里给出不用num且递归的做法:

 1 int rnk(int u,int x){
 2     if(!u) return 1;
 3     int d=cmp(u,x);
 4     int e=rnk(son[u][d<=0?0:1],x);
 5     if(d==-1&&!flag){flag=1; return siz[son[u][0]]+1;}
 6     if(d<=0) return e;
 7     return e+siz[son[u][0]]+1;
 8 }
 9 int kth(int u,int x){
10     if(siz[son[u][0]]+1==x) return w[u];
11     if(x<=siz[son[u][0]]) return kth(son[u][0],x);
12     else return kth(son[u][1],x-siz[son[u][0]]-1);
13 }
14 int pre(int u,int x){
15     int d=cmp(u,x);
16     if(!u) return -inf;
17     if(d==-1) return pre(son[u][0],x);
18     if(d) return max(pre(son[u][1],x),w[u]);
19     return pre(son[u][0],x);
20 }
21 int nxt(int u,int x){
22     int d=cmp(u,x);
23     if(!u) return inf;
24     if(d==-1) return nxt(son[u][1],x);
25     if(!d) return min(nxt(son[u][0],x),w[u]);
26     return nxt(son[u][1],x);
27 }

最后例题:

总代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=100006,inf=1e8;
 4 int son[N][2],w[N],rnd[N],siz[N],cnt=1,t,o,n,q=1,num=0;
 5 bool flag;
 6 inline int read()
 7 {
 8     int T=0,F=1; char ch=getchar();
 9     while(ch<'0'||ch>'9'){if(ch=='-') F=-1; ch=getchar();}
10     while(ch>='0'&&ch<='9') T=(T<<3)+(T<<1)+(ch-48),ch=getchar();
11     return F*T;
12 }
13 void size(int u){siz[u]=siz[son[u][0]]+siz[son[u][1]]+1;}
14 void rotate(int &u,int d){//son[u][d^1] d==0?左旋:右旋。 
15     int p=son[u][d^1];
16     son[u][d^1]=son[p][d];
17     son[p][d]=u;
18     size(u),size(p);//重新统计,先儿子后父亲 
19     u=p;//实际上是将p顶替u的位置 
20 }
21 int cmp(int u,int x){if(w[u]==x) return -1; return w[u]<x;}
22 //判断是左儿子还是右儿子 
23 void ins(int &u,int x)
24 {
25     if(!u){u=++cnt; w[u]=x; rnd[u]=rand()%inf; siz[u]=1;}//新建点 
26     else{
27         ++siz[u];
28         int d=cmp(u,x); if(d==-1) d=0;
29         ins(son[u][d],x);
30         if(rnd[u]<rnd[son[u][d]]) rotate(u,d^1);
31         //插入后如果不满足堆的性质,将插入的数旋上来 
32     }
33 }
34 void del(int &u,int x){
35     int d=cmp(u,x);
36     if(d==-1){
37         if(!(son[u][0]*son[u][1])){u=son[u][0]+son[u][1]; return;}//左或右儿子为空 
38         else{
39             int d2=(rnd[son[u][0]]>rnd[son[u][1]]?1:0);
40             rotate(u,d2); del(son[u][d2],x);
41             //将rnd大的儿子放在父亲处 
42         }
43     }
44     else del(son[u][d],x);
45     size(u);
46 }
47 int rnk(int u,int x){
48     if(!u) return 1;
49     int d=cmp(u,x);
50     int e=rnk(son[u][d<=0?0:1],x);
51     if(d==-1&&!flag){flag=1; return siz[son[u][0]]+1;}
52     if(d<=0) return e;
53     return e+siz[son[u][0]]+1;
54 }
55 int kth(int u,int x){
56     if(siz[son[u][0]]+1==x) return w[u];
57     if(x<=siz[son[u][0]]) return kth(son[u][0],x);
58     else return kth(son[u][1],x-siz[son[u][0]]-1);
59 }
60 int pre(int u,int x){
61     int d=cmp(u,x);
62     if(!u) return -inf;
63     if(d==-1) return pre(son[u][0],x);
64     if(d) return max(pre(son[u][1],x),w[u]);
65     return pre(son[u][0],x);
66 }
67 int nxt(int u,int x){
68     int d=cmp(u,x);
69     if(!u) return inf;
70     if(d==-1) return nxt(son[u][1],x);
71     if(!d) return min(nxt(son[u][0],x),w[u]);
72     return nxt(son[u][1],x);
73 }
74 int main()
75 {
76     srand(time(NULL)); siz[1]=1; w[1]=inf; rnd[1]=inf; memset(son,0,sizeof(son)); n=read();
77     for(register int i=1;i<=n;++i){
78         o=read(); t=read();
79         switch(o){
80             case 1:ins(q,t);break;
81             case 2:del(q,t);break;
82             case 3:flag=0,printf("%d\n",rnk(1,t));break;
83             case 4:printf("%d\n",kth(1,t));break;
84             case 5:printf("%d\n",pre(1,t));break;
85             case 6:printf("%d\n",nxt(1,t));break;
86         }
87     }
88     return 0;
89 }

三.fhq treap:

1.介绍:

fan hao qiang巨佬首创,你值得拥有。

它,操作简单,核心操作只有两项;它,码量极小,给你从未体验过的舒爽感觉;

它,常数巨大,不过跟Splay差不多(略大一点);它,功能多样,插、删、排、k大数、前、后、翻、离、并,样样精通。

你还在等什么,赶紧看完这篇博客,投入到代码的海洋里去吧!

2.特点:

<1>.拥有treap的性质

<2>.两个核心操作:split(分离)和merge(合并):

(1).split(x,k,y,z):将根为x的fhq treap分为权值小于等于k的、以y为根的fhq treap和权值大于k以z为根的fhq treap;

(2).merge(x,y,z):将以x为根的fhq treap和以y为根的fhq treap合为以z为根的fhq treap。(条件:x树中所有数的权值小于y树中任意一数的权值[即:max(x)<min(y)])

<3>.可持久化

<4>.支持区间操作(插入,删除,翻转,修改,求和,最大子序列和)

3.代码:

(1).普通平衡树:

<1>.分离:

真的不知道讲些什么,非常短小精悍,只能靠自己理解。

1 void split(int x,int k,int &l,int &r)
2 {
3     if(!x){l=0,r=0; return;}
4     if(k<w[x]) r=x,split(son[x][0],k,l,son[x][0]),pushup(x);
5     else l=x,split(son[x][1],k,son[x][1],r),pushup(x);
6 }

<2>.合并:

作者表示还是蒻得没话说。

1 void merge(int l,int r,int &o)
2 {
3     if(!l||!r){o=l+r; return;}
4     if(rnd[l]<rnd[r]) o=l,merge(son[l][1],r,son[l][1]),pushup(l);
5     else o=r,merge(l,son[r][0],son[r][0]),pushup(r);
6 }

 <3>.插入:

插入一个数k。

直接将fhq treap分为<=k和>k的两部分t1t2,再将t1k合并为t1t1t2合并为root

1 inline void new_point(int x){siz[++n]=1,rnd[n]=((rand()<<15)|rand()),w[n]=x;}
2 //我是个丧心病狂的压行狂魔
3 void ins(int k)
4 {
5     new_point(k); split(root,k,t1,t2);//new_point是新建一个点 
6     merge(t1,n,root),merge(root,t2,root);
7     //n是权值为k的节点的编号 
8 }

<4>.删除:

插入一个数k。

直接将fhq treap分为<=k和>k的两部分t2和t3,再将t2分为t1<k和t2==k的两部分,t1与t3合并为root即可。

1 void del(int x)
2 {
3     split(root,x,t1,t2),split(t1,x-1,t1,t3);
4     merge(son[t3][0],son[t3][1],t3);
5 } 

 <5>.排名:

求权值k的排名(不包括权值与其相等的)

直接将fhq treap分为权值<k和权值>=k的两部分t1和t2,k的排名为siz[t1]+1

1 split(root,t-1,t1,t2),printf("%d\n",siz[t1]+1),merge(t1,t2,root);

<6>.k大数:

求树中权值第k大数的权值。

实现操作和普通Treap一样,只不过这里用非递归形式,常数更小。

 1 int kth(int x,int k)
 2 //以x为根的树中的第k大数
 3 {
 4     while(1)
 5     {
 6         t=siz[son[x][0]];
 7         if(k<=t) x=son[x][0];
 8         else if(k==t+1) return w[x];
 9         else k=k-t-1,x=son[x][1];
10     }
11 }

<7>.前驱:

求k的前驱。(权值小于k的最大值)

我们可以将树分为小于k的t1和大于等于k的t2两部分,k的前驱即是树t1中的最大权值(即kth(t1,size[t1])。

别忘了重新将树t1和t2合并为root。

1 split(root,t-1,t1,t2),printf("%d\n",kth(t1,siz[t1])),merge(t1,t2,root);

<8>.后继:(权值大于k的最小值)

同理,将树分为小于等于k的t1和大于k的t2两部分,k的后继即是树t2中的最小权值(kth(t2,1)),最后合并。

1 split(root,t,t1,t2),printf("%d\n",kth(t2,1)),merge(t1,t2,root);

总代码:

请叫我压行怪

 1 #include<bits/stdc++.h>
 2 #include<time.h>
 3 #include<stdlib.h>
 4 #define re register
 5 using namespace std;
 6 const int N=100006,inf=1e9; 
 7 int n=0,m,son[N][2],w[N],siz[N],rnd[N],root,t,opt,t1,t2,t3;
 8 inline int read()
 9 {
10     int T=0,F=1; char ch=getchar();
11     while(ch<'0'||ch>'9'){if(ch=='-') F=-1; ch=getchar();}
12     while(ch>='0'&&ch<='9') T=(T<<3)+(T<<1)+(ch-48),ch=getchar();
13     return F*T; 
14 }
15 inline void new_point(int x){siz[++n]=1,rnd[n]=((rand()<<15)|rand()),w[n]=x;}
16 inline void pushup(int x){siz[x]=siz[son[x][0]]+siz[son[x][1]]+1;}
17 void split(int x,int k,int &l,int &r)
18 {
19     if(!x){l=0,r=0; return;}
20     if(k<w[x]) r=x,split(son[x][0],k,l,son[x][0]),pushup(x);
21     else l=x,split(son[x][1],k,son[x][1],r),pushup(x);
22 }
23 void merge(int l,int r,int &o)
24 {
25     if(!l||!r){o=l+r; return;}
26     if(rnd[l]<rnd[r]) o=l,merge(son[l][1],r,son[l][1]),pushup(l);
27     else o=r,merge(l,son[r][0],son[r][0]),pushup(r);
28 }
29 int kth(int x,int k)
30 {
31     while(1)
32     {
33         t=siz[son[x][0]];
34         if(k<=t) x=son[x][0];
35         else if(k==t+1) return w[x];
36         else k=k-t-1,x=son[x][1];
37     }
38 }
39 void ins(int x){new_point(x),split(root,x,t1,t2),merge(t1,n,root),merge(root,t2,root);}
40 void del(int x){split(root,x,t1,t2),split(t1,x-1,t1,t3),merge(son[t3][0],son[t3][1],t3),merge(t1,t3,t1),merge(t1,t2,root);} 
41 int main()
42 {
43     m=read();
44     for(re int i=1;i<=m;++i)
45     {
46         opt=read(),t=read();
47         switch(opt)
48         {
49             case 1:ins(t);break;
50             case 2:del(t);break;
51             case 3:split(root,t-1,t1,t2),printf("%d\n",siz[t1]+1),merge(t1,t2,root);break;
52             case 4:printf("%d\n",kth(root,t));break;
53             case 5:split(root,t-1,t1,t2),printf("%d\n",kth(t1,siz[t1])),merge(t1,t2,root);break;
54             case 6:split(root,t,t1,t2),printf("%d\n",kth(t2,1)),merge(t1,t2,root);break;
55         }
56     }
57     return 0;
58 }

(2).带有区间操作的平衡树:

比一般的平衡树加了个pushdwon(下传标记)以及各自需要支持的操作,

mergesplit中加入pushdown操作,

并且split修改为将一棵树分为<=size>size两部分。

pushup加了许多新东西维护。

1 inline void equal(int x,int y){if(!x) return; w[x]=lazy[x]=y,sum[x]=siz[x]*y,ml[x]=mr[x]=mx[x]=(y>=0?sum[x]:y);}
2 inline void ress(int x){if(!x) return; re[x]^=1,swap(ml[x],mr[x]),swap(son[x][0],son[x][1]);}
3 inline void pushdown(int x)
4 {
5     if(!x) return; 
6     int lc=son[x][0],rc=son[x][1];
7     if(re[x]) re[x]=0,ress(lc),ress(rc);
8     if(lazy[x]!=inf2) equal(son[x][0],lazy[x]),equal(son[x][1],lazy[x]),lazy[x]=inf2;
9 }
1 inline void pushup(int x){
2     int lc=son[x][0],rc=son[x][1];
3     siz[x]=siz[lc]+siz[rc]+1;
4     sum[x]=sum[lc]+sum[rc]+w[x];
5     ml[x]=max(ml[lc],max(sum[lc]+w[x],sum[lc]+w[x]+ml[rc]));
6     mr[x]=max(mr[rc],max(sum[rc]+w[x],sum[rc]+w[x]+mr[lc]));
7     mx[x]=max(mx[lc],max(mx[rc],max(mr[lc],0)+w[x]+max(ml[rc],0)));
8 }

 

 1 void split(int x,int k,int &l,int &r){
 2     if(!x){l=r=0; return;}
 3     pushdown(x); p=siz[son[x][0]]+1;
 4     if(p<=k) l=x,split(son[x][1],k-p,son[x][1],r),pushup(x);
 5     else r=x,split(son[x][0],k,l,son[x][0]),pushup(x);
 6 }
 7 void merge(int l,int r,int &o){
 8     if(!l||!r){o=(l|r); pushdown(o); return;} 
 9     if(rnd[l]<rnd[r]) pushdown(l),o=l,merge(son[l][1],r,son[l][1]),pushup(l);
10     else pushdown(r),o=r,merge(l,son[r][0],son[r][0]),pushup(r);
11 }

解释一下数组含义:

siz[x]:子树大小

rnd[x]:随机值

w[x]:权值

sum[x]:子树内权值总和

lazy[x]:懒标记,代表子树内所有点被修改为lazy[x]

re[x]:懒标记,代表子树内所有点都被翻转/没被翻转

son[x][0/1]:左右儿子

ml[x]:从子树内最左端点开始的最大连续子段和

mr[x]:从子树内最右端点开始的最大连续子段和

mx[x]:子树内最大连续子段和

我们可以发现:

 

对于每一个区间[l,r],我们将root分为[1,l-1],[l,r],[r+1,n]三部分,操作完再合并。

1 split(root,r,t1,t3),split(t1,l-1,t1,t2);
2 //bula bula bula~~~;
3 merge(t2,t3,t2),merge(t1,t2,root);
4 //注意:split(root,l-1,t1,t2),split(t2,r,t2,t3)是错误的
5 //在将root分为树t1、t2后,r在t2中处于第r-l+1个位置
6 //split(root,l-1,t1,t2),split(t2,r-l+1,t2,t3);

<1>.翻转:

1 void res(int x,int k){split(root,x+k-1,t2,t3),split(t2,x-1,t1,t2),ress(t2),merge(t1,t2,t1),merge(t1,t3,root);}

<2>.修改:

1 void chg(int x,int k,int y){split(root,x+k-1,t2,t3),split(t2,x-1,t1,t2),equal(t2,y),merge(t1,t2,t1),merge(t1,t3,root);}

 <3>.求和:

1 void gtsum(int x,int k){split(root,x+k-1,t2,t3),split(t2,x-1,t1,t2),printf("%d\n",sum[t2]),merge(t1,t2,t1),merge(t1,t3,root);}

 <4>.插入:

要求线性建树,因此要用到笛卡尔树建树法:

笛卡尔树:

满足Treap,key值左儿子小于本身小于右儿子,val值祖先大/小于孙辈的性质,(不妨设val值越大,深度越高)。

因此常用于线性建Treap。

其方法大致如下:

首先将点按key值排序,后插入的点一定为先插入的点的右儿子

维护一个关于val单调栈,依次存放根,根的右儿子,根的右儿子的右儿子,根的右儿子的右儿子的右儿子………

每次新加入节点x,将val值大于x的点从栈里清除,x变为val值小于x且相差x最小的点y的右儿子,y点原先的右儿子z变为x的左儿子。

我们可以发现:x、y、z间恰好满足:val[z]>val[x]>val[y],key[x]>key[z]>key[y],符合Treap的性质。

 1 void ins(int x,int k){
 2     rt=0,t=0;
 3     for(int i=1;i<=k;++i){
 4         t1=read(),new_point(t1); 
 5         while(rnd[num]<rnd[s[t]]&&t>=1) pushup(s[t]),son[num][0]=s[t],--t;
 6         if(t) son[s[t]][1]=num;
 7         else rt=num;
 8         s[++t]=num;
 9     }
10     while(t>=1) pushup(s[t]),--t;
11     split(root,x,t1,t2);
12     merge(t1,rt,t1);
13     merge(t1,t2,root);
14 }

 <5>.删除:

1 void del(int x,int k){split(root,x+k-1,t2,t3),split(t2,x-1,t1,t2),merge(t1,t3,root),add(t2);}

 <6>.最大连续子段和:

1 void mxsum(int x,int k){split(root,x+k-1,t2,t3),split(t2,x-1,t1,t2),printf("%d\n",mx[t2]),merge(t1,t2,t1),merge(t1,t3,root);}

例题:

总代码:

 1 // luogu-judger-enable-o2
 2 #include<bits/stdc++.h>
 3 #define reg register
 4 using namespace std;
 5 const int N=5e5+6,inf=-(1<<30),inf2=-1006;
 6 int n,m,tot=0,top=0,num,t,root=0,rt,t1,t2,t3,p,s[N],re[N],son[N][2],bin[N],siz[N],sum[N],w[N],ml[N],mr[N],mx[N],rnd[N],lazy[N];
 7 bool flag=0;
 8 char c[33];
 9 inline int read(){
10     int T=0,F=1; char ch=getchar();
11     while(ch<'0'||ch>'9'){if(ch=='-') F=-1; ch=getchar();}
12     while(ch>='0'&&ch<='9') T=(T<<3)+(T<<1)+(ch-48),ch=getchar();
13     return F*T;
14 }
15 inline void new_point(int x){
16     if(top) num=bin[top],--top;
17     else num=++tot; 
18     lazy[num]=inf2,re[num]=0,siz[num]=1,son[num][0]=son[num][1]=0,ml[num]=mr[num]=mx[num]=sum[num]=w[num]=x,rnd[num]=((rand()<<15)|rand());
19 }
20 inline void pushup(int x){
21     int lc=son[x][0],rc=son[x][1];
22     siz[x]=siz[lc]+siz[rc]+1;
23     sum[x]=sum[lc]+sum[rc]+w[x];
24     ml[x]=max(ml[lc],max(sum[lc]+w[x],sum[lc]+w[x]+ml[rc]));
25     mr[x]=max(mr[rc],max(sum[rc]+w[x],sum[rc]+w[x]+mr[lc]));
26     mx[x]=max(mx[lc],max(mx[rc],max(mr[lc],0)+w[x]+max(ml[rc],0)));
27 }
28 inline void equal(int x,int y){if(!x) return; w[x]=lazy[x]=y,sum[x]=siz[x]*y,ml[x]=mr[x]=mx[x]=(y>=0?sum[x]:y);}
29 inline void ress(int x){if(!x) return; re[x]^=1,swap(ml[x],mr[x]),swap(son[x][0],son[x][1]);}
30 inline void pushdown(int x)
31 {
32     if(!x) return; 
33     int lc=son[x][0],rc=son[x][1];
34     if(re[x]) re[x]=0,ress(lc),ress(rc);
35     if(lazy[x]!=inf2) equal(son[x][0],lazy[x]),equal(son[x][1],lazy[x]),lazy[x]=inf2;
36 }
37 void split(int x,int k,int &l,int &r){
38     if(!x){l=r=0; return;}
39     pushdown(x); p=siz[son[x][0]]+1;
40     if(p<=k) l=x,split(son[x][1],k-p,son[x][1],r),pushup(x);
41     else r=x,split(son[x][0],k,l,son[x][0]),pushup(x);
42 }
43 void merge(int l,int r,int &o){
44     if(!l||!r){o=(l|r); pushdown(o); return;} 
45     if(rnd[l]<rnd[r]) pushdown(l),o=l,merge(son[l][1],r,son[l][1]),pushup(l);
46     else pushdown(r),o=r,merge(l,son[r][0],son[r][0]),pushup(r);
47 }
48 void ins(int x,int k){
49     rt=0,t=0;
50     for(int i=1;i<=k;++i){
51         t1=read(),new_point(t1); 
52         while(rnd[num]<rnd[s[t]]&&t>=1) pushup(s[t]),son[num][0]=s[t],--t;
53         if(t) son[s[t]][1]=num;
54         else rt=num;
55         s[++t]=num;
56     }
57     while(t>=1) pushup(s[t]),--t;
58     split(root,x,t1,t2);
59     merge(t1,rt,t1);
60     merge(t1,t2,root);
61 }
62 void add(int x){if(!x) return; bin[++top]=x,add(son[x][0]),add(son[x][1]);}
63 void del(int x,int k){split(root,x+k-1,t2,t3),split(t2,x-1,t1,t2),merge(t1,t3,root),add(t2);}
64 void chg(int x,int k,int y){split(root,x+k-1,t2,t3),split(t2,x-1,t1,t2),equal(t2,y),merge(t1,t2,t1),merge(t1,t3,root);}
65 void res(int x,int k){split(root,x+k-1,t2,t3),split(t2,x-1,t1,t2),ress(t2),merge(t1,t2,t1),merge(t1,t3,root);}
66 void gtsum(int x,int k){split(root,x+k-1,t2,t3),split(t2,x-1,t1,t2),printf("%d\n",sum[t2]),merge(t1,t2,t1),merge(t1,t3,root);}
67 void mxsum(int x,int k){split(root,x+k-1,t2,t3),split(t2,x-1,t1,t2),printf("%d\n",mx[t2]),merge(t1,t2,t1),merge(t1,t3,root);}
68 int main(){
69     n=read(),m=read();
70     ins(0,n); ml[0]=mr[0]=mx[0]=-(1<<30); w[0]=sum[0]=siz[0]=0; lazy[0]=-1006;
71     for(reg int i=1;i<=m;++i){
72         scanf("%s",c); t=strlen(c);
73         if(c[0]=='I') t1=read(),t2=read(),ins(t1,t2);
74         else if(c[0]=='D') t1=read(),t2=read(),del(t1,t2);
75         else if(c[0]=='R') t1=read(),t2=read(),res(t1,t2);
76         else if(c[0]=='cG'&&t>3) t1=read(),t2=read(),gtsum(t1,t2);
77         else if(c[0]=='G') t1=read(),gtsum(t1,1);
78         else if(c[2]=='K') t1=read(),t2=read(),t3=read(),chg(t1,t2,t3);
79         else t1=read(),t2=read(),mxsum(t1,t2);
80     }
81     return 0;
82 }

(3).可持久化:(有多个历史版本,每一次修改基于指定的版本)

运用了主席树的思想,如有不懂可以先看我的博客: 主席树

我们每一次操作最多只会改变一条链。

因此,我们每次操作只要复制那条链,并在上面进行修改,其它的点只要沿用之前的即可,时间和空间被大大优化。

重点copy(复制)函数和加了copy函数的split和merge

 1 inline void copy(int x){w[++n]=w[x],son[n][0]=son[x][0],son[n][1]=son[x][1],rnd[n]=rnd[x];}
 2 void split(int x,int k,int &l,int &r)
 3 {
 4     if(!x){l=0,r=0; return;}
 5     if(w[x]<=k) copy(x),l=x=n,split(son[x][1],k,son[x][1],r),pushup(x);
 6     else copy(x),r=x=n,split(son[x][0],k,l,son[x][0]),pushup(x); 
 7 }
 8 void merge(int l,int r,int &o)
 9 {
10     if(!l||!r){o=l+r; return;}
11     if(rnd[l]<rnd[r]) copy(l),o=l=n,merge(son[l][1],r,son[l][1]),pushup(l);
12     else copy(r),o=r=n,merge(l,son[r][0],son[r][0]),pushup(r);
13

<1>.普通平衡树:

例题:

代码:

 1 #include<bits/stdc++.h>
 2 #define re register
 3 using namespace std;
 4 const int N=5e5+6;
 5 int n=0,m,siz[N<<6],w[N<<6],rnd[N<<6],son[N<<6][2],rt[N],t1,t2,t3,p,opt;
 6 inline int read()
 7 {
 8     int T=0,F=1; char ch=getchar();
 9     while(ch<'0'||ch>'9'){if(ch=='-') F=-1; ch=getchar();}
10     while(ch>='0'&&ch<='9') T=(T<<3)+(T<<1)+(ch-48),ch=getchar();
11     return F*T;
12 }
13 inline void pushup(int x){siz[x]=siz[son[x][0]]+siz[son[x][1]]+1;}
14 inline void new_point(int x){siz[++n]=1; w[n]=x; rnd[n]=((rand()<<15)|rand());}
15 inline void copy(int x){w[++n]=w[x],son[n][0]=son[x][0],son[n][1]=son[x][1],rnd[n]=rnd[x];}
16 void split(int x,int k,int &l,int &r)
17 {
18     if(!x){l=0,r=0; return;}
19     if(w[x]<=k) copy(x),l=x=n,split(son[x][1],k,son[x][1],r),pushup(x);
20     else copy(x),r=x=n,split(son[x][0],k,l,son[x][0]),pushup(x); 
21 }
22 void merge(int l,int r,int &o)
23 {
24     if(!l||!r){o=l+r; return;}
25     if(rnd[l]<rnd[r]) copy(l),o=l=n,merge(son[l][1],r,son[l][1]),pushup(l);
26     else copy(r),o=r=n,merge(l,son[r][0],son[r][0]),pushup(r);
27 }
28 void ins(int &root,int x){split(root,x,t1,t2),new_point(x),merge(t1,n,t1),merge(t1,t2,root);}
29 void del(int &root,int x){split(root,x,t1,t2),split(t1,x-1,t1,t3),merge(son[t3][0],son[t3][1],t3),merge(t1,t3,t1),merge(t1,t2,root);}
30 int kth(int x,int k)
31 {
32     while(k)
33     {
34         p=siz[son[x][0]];
35         if(k<=p) x=son[x][0];
36         else if(k==p+1) return w[x];
37         else x=son[x][1],k=k-p-1;
38     }
39 }
40 int main()
41 {
42     m=read();
43     for(re int i=1;i<=m;++i)
44     {
45         t1=read(),opt=read(),t3=read();
46         rt[i]=rt[t1];
47         switch(opt)
48         {
49             case 1:ins(rt[i],t3);break;
50             case 2:del(rt[i],t3);break;
51             case 3:split(rt[i],t3-1,t1,t2),printf("%d\n",siz[t1]+1),merge(t1,t2,rt[i]);break;
52             case 4:printf("%d\n",kth(rt[i],t3));break;
53             case 5:split(rt[i],t3-1,t1,t2),printf("%d\n",kth(t1,siz[t1])),merge(t1,t2,rt[i]);break;
54             case 6:split(rt[i],t3,t1,t2),printf("%d\n",kth(t2,1)),merge(t1,t2,rt[i]);break;
55         }
56     }
57     return 0;
58 }

 

<2>.带有区间操作的平衡树:

目前只知道带翻转的例题

copy函数外,pushdown函数也有较大变化:

re[x]=1,则左右儿子的re值都会改变,但分裂或者合并时只会遍历一个儿子,故在pushdown时也进行copy

 1 inline void pushdown(int x){
 2     if(re[x]){
 3         int lc=son[x][0],rc=son[x][1];
 4         if(lc) copy(lc),lc=son[x][0]=n;
 5         if(rc) copy(rc),rc=son[x][1]=n;
 6         re[lc]^=1,re[rc]^=1;
 7         swap(son[x][0],son[x][1]); re[x]=0;
 8     }
 9 }
10 void split(int x,int k,int &l,int &r)
11 {
12     if(!x){l=r=0; return;}
13     pushdown(x),p=siz[son[x][0]];
14     //if(siz[x]==k){copy(x); l=x=n,r=0; return;}
15     if(p<k) copy(x),l=x=n,split(son[x][1],k-p-1,son[x][1],r),pushup(x);
16     else copy(x),r=x=n,split(son[x][0],k,l,son[x][0]),pushup(x); 
17 }
18 void merge(int l,int r,int &o){
19     if(!l||!r){o=l+r; return;}
20     if(rnd[l]<rnd[r]) pushdown(l),o=l,merge(son[l][1],r,son[l][1]),pushup(l);
21     else pushdown(r),o=r,merge(l,son[r][0],son[r][0]),pushup(r);
22 }

  

 1 #include<bits/stdc++.h>
 2 #define reg register
 3 using namespace std;
 4 const int N=2e5+6;
 5 int n=0,m,p,opt,t1,t2,t3,siz[N<<6],son[N<<6][2],w[N<<6],re[N<<6],rnd[N<<6],rt[N];
 6 long long sum[N<<6],t=0;
 7 inline int read()
 8 {
 9     int T=0,F=1; char ch=getchar();
10     while(ch<'0'||ch>'9'){if(ch=='-') F=-1; ch=getchar();}
11     while(ch>='0'&&ch<='9') T=(T<<3)+(T<<1)+(ch-48),ch=getchar();
12     return F*T;
13 }
14 inline void new_point(int x){++n,siz[n]=1,sum[n]=w[n]=x,rnd[n]=((rand()<<15)|rand());}
15 inline void copy(int x){++n,w[n]=w[x],sum[n]=sum[x],siz[n]=siz[x],son[n][0]=son[x][0],son[n][1]=son[x][1],re[n]=re[x],rnd[n]=rnd[x];}
16 inline void pushup(int x){siz[x]=siz[son[x][0]]+siz[son[x][1]]+1,sum[x]=sum[son[x][0]]+sum[son[x][1]]+w[x];}
17 inline void pushdown(int x){
18     if(re[x]){
19         int lc=son[x][0],rc=son[x][1];
20         if(lc) copy(lc),lc=son[x][0]=n;
21         if(rc) copy(rc),rc=son[x][1]=n;
22         re[lc]^=1,re[rc]^=1;
23         swap(son[x][0],son[x][1]); re[x]=0;
24     }
25 }
26 void split(int x,int k,int &l,int &r)
27 {
28     if(!x){l=r=0; return;}
29     pushdown(x),p=siz[son[x][0]];
30     //if(siz[x]==k){copy(x); l=x=n,r=0; return;}
31     if(p<k) copy(x),l=x=n,split(son[x][1],k-p-1,son[x][1],r),pushup(x);
32     else copy(x),r=x=n,split(son[x][0],k,l,son[x][0]),pushup(x); 
33 }
34 void merge(int l,int r,int &o){
35     if(!l||!r){o=l+r; return;}
36     if(rnd[l]<rnd[r]) pushdown(l),o=l,merge(son[l][1],r,son[l][1]),pushup(l);
37     else pushdown(r),o=r,merge(l,son[r][0],son[r][0]),pushup(r);
38 }
39 void ins(int x,int y,int &root){split(root,x,t1,t2),new_point(y),merge(t1,n,t1),merge(t1,t2,root);} 
40 void del(int x,int &root){split(root,x,t1,t2),split(t1,x-1,t1,t3),merge(t1,t2,root);}
41 void res(int l,int r,int &root){split(root,r,t1,t3),split(t1,l-1,t1,t2),re[t2]^=1,merge(t1,t2,t1),merge(t1,t3,root);}
42 long long query(int l,int r,int &root){split(root,r,t1,t3),split(t1,l-1,t1,t2),t=sum[t2],merge(t1,t2,t1),merge(t1,t3,root); return t;}
43 int main()
44 {
45     m=read(); long long e,f;
46     for(reg int i=1;i<=m;++i){
47         t1=read(),opt=read(),e=read(),e^=t,rt[i]=rt[t1];
48         switch(opt){
49             case 1:f=read(),f^=t,ins(e,f,rt[i]);break;
50             case 2:del(e,rt[i]);break;
51             case 3:f=read(),f^=t,res(e,f,rt[i]);break;
52             case 4:f=read(),f^=t,printf("%lld\n",query(e,f,rt[i]));break;
53         }
54     }
55     return 0;
56 }

 

四.splay

1.介绍:老牌平衡树算法,由tarjan大神发明,用神奇的伸展操作维护平衡并进行操作。

2.特点:

<1>.很多操作后都喜欢伸展维护平衡,故常数巨大。

<2>.splay操作,splay(x),将x变为根。

操作如图:

[1].三点不一线:将4连续旋转两次。

[2].三点一线:先转父亲2再转儿子3。

3.代码:

区间操作跟fhq treap差不多,其它操作跟treap差不多。

不过,处理区间时一般将l-1旋到根,r+1旋到右儿子,r+1的左子树就是区间[l,r];

这样万一遇到l=1时,l-1=0,f[0]=0,root会为0,而son[0][0]=son[0][1]=0,易出错,

同理r=n时也是如此,所以新增0和n+1两个点,但为防止误判点0不存在,0到n+1的编号就都右移一位,对应1到n+2

读入lr,却splay(l,0),splay(r+2,l)便是因此。

<1>.普通平衡树:

 

  1 #include<bits/stdc++.h>
  2 #define re register
  3 using namespace std;
  4 const int N=2e5+6;
  5 int n,w[N],son[N][2],f[N],siz[N],num[N],cnt=0,head[N],t1,opt,o,root;
  6 inline int read()
  7 {
  8     int T=0,F=1; char ch=getchar();
  9     while(ch<'0'||ch>'9'){if(ch=='-') F=-1; ch=getchar();}
 10     while(ch>='0'&&ch<='9') T=(T<<3)+(T<<1)+(ch-48),ch=getchar();
 11     return F*T;
 12 }
 13 inline void pushup(int x){siz[x]=siz[son[x][0]]+siz[son[x][1]]+num[x];}
 14 inline void rotate(int x)
 15 {
 16     int y=f[x]; int z=f[y],k=(son[y][1]==x);
 17     if(z) son[z][son[z][1]==y]=x;
 18     f[x]=z;
 19     son[y][k]=son[x][k^1],f[son[y][k]]=y;
 20     son[x][k^1]=y,f[y]=x;
 21     pushup(y),pushup(x);
 22 }
 23 void splay(int x,int u)
 24 {
 25     while(f[x]!=u)
 26     {
 27         int y=f[x]; int z=f[y];
 28         if(z!=u) rotate(((son[y][1]==x)^(son[z][1]==y))?x:y);
 29         rotate(x);
 30     }
 31     if(!u) root=x;
 32 }
 33 int fnd(int x,int u)
 34 {
 35     while(w[x]!=u&&x) x=son[x][u>w[x]];
 36     return x;
 37 }
 38 void ins(int x,int u)
 39 {
 40     if(!x){++o,w[1]=u,siz[1]=num[1]=1,f[1]=0,root=1; return;}
 41     int t=son[x][w[x]<u];
 42     while(w[x]!=u&&t) ++siz[x],x=t,t=son[x][w[x]<u];
 43     if(w[x]!=u) ++siz[x],++o,w[o]=u,siz[o]=num[o]=1,son[x][w[x]<u]=o,f[o]=x,splay(o,0);
 44     else ++num[x],splay(x,0);
 45 }
 46 int rak(int u){u=fnd(root,u); splay(u,0); return siz[son[u][0]]+1;}
 47 int kth(int x,int u)
 48 {
 49     int t;
 50     while(1)
 51     {
 52         t=siz[son[x][0]]+num[x];
 53         if(u>t) u-=t,x=son[x][1];
 54         else if(u>siz[son[x][0]]&&u<=t) return w[x];
 55         else x=son[x][0];
 56     }
 57 }
 58 int lat(int u)
 59 {
 60     u=son[u][0];
 61     if(!u) return -1;
 62     while(son[u][1]) u=son[u][1];
 63     return u;
 64 }
 65 void del(int u)
 66 {
 67     int t=fnd(root,u);
 68     splay(t,0);
 69     if(num[t]>1) --num[t],--siz[t];
 70     else
 71     {
 72         t=lat(t);
 73         if(t==-1){root=son[root][1],f[root]=0; return;}
 74         splay(t,0);
 75         son[t][1]=son[son[t][1]][1],f[son[t][1]]=t;
 76         --siz[t];
 77     }
 78 }
 79 int pre(int u)
 80 {
 81     int t;
 82     ins(root,u),t=lat(root),del(w[root]);
 83     return w[t];
 84 }
 85 int nxt(int u)
 86 {
 87     int t;
 88     ins(root,u);
 89     t=son[root][1];
 90     while(son[t][0]) t=son[t][0];
 91     del(w[root]);
 92     return w[t];
 93 }
 94 int main()
 95 {
 96     n=read(); o=0; root=0;
 97     for(re int i=1;i<=n;++i)
 98     {
 99         opt=read(),t1=read();
100         switch(opt)
101         {
102             case 1:ins(root,t1);break;
103             case 2:del(t1);break;
104             case 3:printf("%d\n",rak(t1));break;
105             case 4:printf("%d\n",kth(root,t1));break;
106             case 5:printf("%d\n",pre(t1));break;
107             case 6:printf("%d\n",nxt(t1));break;
108         }
109     }
110     return 0; 
111 }

<2>.文艺平衡树: 

 1 #include<bits/stdc++.h>
 2 #define reg register
 3 using namespace std;
 4 const int N=2e5+6;
 5 int n,w[N],son[N][2],f[N],siz[N],t1,t2,o,root,m,re[N];
 6 inline int read()
 7 {
 8     int T=0,F=1; char ch=getchar();
 9     while(ch<'0'||ch>'9'){if(ch=='-') F=-1; ch=getchar();}
10     while(ch>='0'&&ch<='9') T=(T<<3)+(T<<1)+(ch-48),ch=getchar();
11     return F*T;
12 }
13 inline void pushup(int x){siz[x]=siz[son[x][0]]+siz[son[x][1]]+1;}
14 inline void rotate(int x)
15 {
16     int y=f[x]; int z=f[y],k=(son[y][1]==x);
17     if(z) son[z][son[z][1]==y]=x;
18     f[x]=z;
19     son[y][k]=son[x][k^1],f[son[y][k]]=y;
20     son[x][k^1]=y,f[y]=x;
21     pushup(y),pushup(x);
22 }
23 void splay(int x,int u)
24 {
25     while(f[x]!=u)
26     {
27         int y=f[x]; int z=f[y];
28         if(z!=u) rotate((son[y][1]==x)^(son[z][1]==y)?x:y);
29         rotate(x);
30     }
31     if(!u) root=x;
32 }
33 void ins(int x,int u)
34 {
35     if(!x){++o,w[1]=u,siz[1]=1,f[1]=0,root=1; return;}
36     int t=son[x][w[x]<u];
37     while(t) ++siz[x],x=t,t=son[x][w[x]<u];
38     ++siz[x],++o,w[o]=u,siz[o]=1,son[x][w[x]<u]=o,f[o]=x,splay(o,0);
39 }
40 int kth(int x,int u)
41 {
42     int t;
43     while(1)
44     {
45         if(re[x]) re[x]=0,re[son[x][0]]^=1,re[son[x][1]]^=1,swap(son[x][0],son[x][1]);
46         t=siz[son[x][0]]+1;
47         if(u>t) u-=t,x=son[x][1];
48         else if(u==t) return w[x];
49         else x=son[x][0];
50     }
51 }
52 void res(int l,int r)
53 {
54     l=kth(root,l),r=kth(root,r+2);
55     splay(l,0),splay(r,l);
56     l=son[r][0],re[l]^=1;
57 }
58 void output(int x)
59 {
60     if(!x) return;
61     if(re[x]) swap(son[x][0],son[x][1]),re[son[x][0]]^=1,re[son[x][1]]^=1;
62     output(son[x][0]);
63     if(x!=1&&x!=n+2) printf("%d ",x-1);
64     output(son[x][1]);
65 }
66 int main()
67 {
68     n=read(),m=read(); o=0; root=0;
69     for(reg int i=1;i<=n+2;++i) ins(root,i);
70     for(reg int i=1;i<=m;++i) t1=read(),t2=read(),res(t1,t2);
71     output(root);
72     return 0; 
73 }

 

posted @ 2019-08-08 14:58  lsoi_ljk123  阅读(315)  评论(0编辑  收藏  举报