平衡树概述

平衡树是排序二叉树的(SBT)的升级版。就是采取各种手段让排序二叉树变得更平衡,不然在数据极端的情况下,SBT退化成一条链,复杂度也退化成 n2 n 2 了。总的来说,就是为了不被丧病出题人卡极端数据发明的算法。

我们直接把三种写法放在一起(可能会有点长,不过将就看吧)。

平衡树——替罪羊树概述

自从写了平衡树之后就整个人都不好了,我是个蒟蒻,打个200行的代码就不咋调得出来。所以在平衡树三个算法里先选了一个最简单的写。

替罪羊树的思想是什么呢。

对于某一个排序二叉树,当它不那么平衡的时候,我们就直接把它全部拆点重新建树。听起来是个十分暴力的做法,但是在暴力建树之后,对于查询我们的复杂度是不会超过大约 2logn 2 ∗ l o g n 的。这个做法被称为朝鲜树(为啥叫这个我也不知道)。

然后我们考虑对于这个做法进行优化:在某一个子树中不平衡,我们只用重构这棵子树,把其它子树重构是没有什么意义的。这样就是替罪羊树了。算法思想是很容易理解的,但是在实现上有一些技巧,等一会写代码的时候再讲。

当然这里还有一个问题:什么时候这一棵子树是不平衡的?我在什么时候重构它?在这里我们引入一个参数 α α ,表示当以u为根的树,如果(这棵树的一个子树大小/这棵树的大小)大于 α α ,我们便重构这棵树。在网上看到各种大佬亲测发现α 0.740.76 0.74 − 0.76 之间是比较科学的。便于理解,我们思考一下 α α 等于1和0.5的意义。当α为1时,这棵树是永远不会重构的,相当于你写了一个板的排序二叉树。当 α α 为0.5时,每加一个点都会进行重构,这样会比α=1还爆炸。

然后是代码:

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<algorithm>  
#include<stack>  
using namespace std;  
struct Tree{//这个表示排序二叉树的节点,和其它算法不同的是,除了要记儿子的实际数目外,还要记录一下儿子的节点数量,这个专门用来判断是否重构  
    int x,lson,rson,son;  
    int num;  
    double size;  
}b[100005];  
int n,reid,fa,hack=0;  
int rea[100005],rep=0,mmp[100005];  
double re=0.74;  
stack <int> d;  

void dfs(int u)  
{  
    if(u==0)return;  
    dfs(b[u].lson);  
    if(b[u].num!=0)//如果这一个点被删掉了就不要它了~   
    {  
      rep++;  
      rea[rep]=b[u].x;  
      mmp[rep]=b[u].num;  
    }  
    d.push(u);  
    dfs(b[u].rson);  
    b[u].lson=0;b[u].rson=0;  
}  

int rebuild(int &u,int l,int r)//传递指针,这很重要  
{  
    int mid=(l+r)/2;//每层取最中间的点作为根,然后把数组分为左右两边递归建树,这样一定是最优的  
    u=d.top();  
    d.pop();  
    b[u].x=rea[mid];  
    b[u].num=mmp[mid];  
    b[u].son=mmp[mid];  
    if(mid-1>=l) b[u].son+=rebuild(b[u].lson,l,mid-1);  
    if(mid+1<=r) b[u].son+=rebuild(b[u].rson,mid+1,r);  
    b[u].size=r-l+1;  
    return b[u].son;  
}  

int insert(int &u,int k)//建树的时候我们可以像线段树一样传儿子指针,可以在下一层修改上一层的儿子指针。  
{  
    if(u==0)  
    {  
        u=d.top();  
        d.pop();  
        b[u].x=k;  
        b[u].size=1;  
        b[u].son=1;  
        b[u].num=1;  
        return 1;  
    }  
    int y=0;  
    if(b[u].x<k)  
      y=insert(b[u].rson,k);  
    else if(b[u].x>k)  
      y=insert(b[u].lson,k);  
    else if(b[u].x==k)  
      b[u].num++;  
    b[u].size=b[u].size+y;  
    b[u].son++;//下面开始是判断这棵子树是否需要重建,由于是递归的,我们查找的是最上面需要重构的点。  
    if(max(b[b[u].lson].size,b[b[u].rson].size)/b[u].size>re)  
      reid=u;  
    if(reid==b[u].lson||reid==b[u].rson)  
      fa=u;  
    return y;  
}//在构造函数里需要维护每个节点的各种参数,这里不详细说明了  

void dele(int u,int x)//删除点操作:只需要把那个位置减一然后维护父亲就好  
{  
    b[u].son--;  
    if(b[u].x==x)  
      b[u].num--;  
    else if(b[u].x>x)  
      dele(b[u].lson,x);  
    else if(b[u].x<x)  
      dele(b[u].rson,x);  
}  

int rankth(int u,int x)//查找数k排在第几个显然可以递归,如果根节点小于k,在左子树里查找第k大的就好,如果大于k的话,就在右子树里查找  
{  
    if(b[u].x==x&&b[u].num!=0)  
    {  
      hack=b[u].num;  
      return b[b[u].lson].son+1;  
    }  
    if(u==0)return 1;  
    if(b[u].x>=x)  
      return rankth(b[u].lson,x);  
    else if(b[u].x<x)  
      return rankth(b[u].rson,x)+b[b[u].lson].son+b[u].num;     
}  

int findit(int u,int k)//查找排名为第k的数,这个递归更简单和上面的一个类似  
{  
    if(k<=b[b[u].lson].son+b[u].num&&k>b[b[u].lson].son)return b[u].x;  
    else if(b[b[u].lson].son>=k)  
      return findit(b[u].lson,k);  
    else if(b[b[u].lson].son+b[u].num<k)  
      return findit(b[u].rson,k-b[b[u].lson].son-b[u].num);  
}  

int main()  
{  
    int root=0;  
    scanf("%d",&n);  
    for(int i=1;i<=n;i++)d.push(i);//用一个栈来存一下可以用的下标,避免空间爆炸   
    for(int i=1;i<=n;i++)  
    {  
        int x,k;  
        scanf("%d%d",&x,&k);  
        if(x==1)  
        {  
            reid=0;  
            insert(root,k);  
            if(reid!=0)//当需要重构的时候我们调用重构函数  
            {  
                rep=0;  
                dfs(reid);//首先dfs一遍,把整棵子树放进一个数组里,由于排序二叉树的性质,这个数组里是有序的  
                if(reid==root) rebuild(root,1,rep);  
                else if(b[fa].lson==reid) rebuild(b[fa].lson,1,rep);  
                else if(b[fa].rson==reid) rebuild(b[fa].rson,1,rep);  
            }  
        }  
        if(x==2) dele(root,k);  
        if(x==3) printf("%d\n",rankth(root,k));  
        if(x==4) printf("%d\n",findit(root,k));  
        if(x==5)//这个是查询数k的前驱,调用两个函数即可  
        {  
            hack=0;int q=rankth(root,k);  
            printf("%d\n",findit(root,q-1));  
        }  
        if(x==6)  
        {  
            hack=0;int q=rankth(root,k);  
            printf("%d\n",findit(root,q+hack));  
        }//值得注意的是我们用一个hack来记录数k在平衡树中出现的次数,这样才能查到正确答案  
    }  
}  





平衡树——splay概述

写了洛谷上的文艺平衡树(传送门!!!)之后,感觉对平衡树有了进一步的理解,所以来写一发博客。首先要明确的一点是,不止排序二叉树用到的是平衡树,换一种说法就是平衡树不止可以维护数列的大小关系。广义来说,它可以维护一段数列的顺序,不管是 treap t r e a p splay s p l a y 还是替罪羊,在旋转或者重构的时候都不会改变中序遍历的答案,尽管这个中序遍历不一定是从小到大排列。由此,排序二叉树是一种特殊情况,它维护的序列恰好是一个从小到大的排列。

废话说了一大堆,现在我们来讲一下 splay s p l a y 是什么。 splay s p l a y 是一种比较玄学的东西,感觉发明它的人好聪明! splay s p l a y 比其它两种多出来的操作就是,它可以把一个节点转到它的任意一个祖先节点上去,而且在旋转的时候遵守某种规则,可以顺带把树变得平衡起来。怎么转的,具体如下:

这里写图片描述

简单来说就是直的转爸爸,弯的转儿子。

至于为什么我也不知道,蒟蒻不喜欢证明。听说可以势能证明一波复杂度均摊下来是 nlogn n l o g n 。反正记住直的转爸爸,弯的转儿子。

重要的话说三遍:

爸爸是直的,儿子是弯的!

爸爸是直的,儿子是弯的!!

爸爸是直的,儿子是弯的!!!

然后这一段代码实现很有技巧,待会儿说代码的时候再说。

接着我们可以发现一些奇奇怪怪的性质,可以看一看这道板子题:P3369 【模板】普通平衡树

这道题要求我们要满足6个操作,我们来一个一个讲:

1.插入

插入和其它两种是特别像的,唯一不同的一点就是如果插入建立了一个新的节点,要把这个节点直接转到根。这样的目的是保证复杂度。然后好像就没什么了

2.删除

删除是splay里比较重要的操作,和其它两种完全不一样,如果需要删除一个节点的话,我们先把它转到根,然后把它的前驱转到根,接着我们可以发现要删掉的这个节点一定只有一个右儿子,直接删掉即可。是不是有点晕呢?画个图感受一下。

这里写图片描述

假设要删除的节点还有左儿子,它的左儿子一定比它小,且比它的前驱大。显然没有这样的节点。当然还要特判一下,假如它没有前驱,直接把根变作它的右儿子即可。

3.排名

没啥说的,和其它的一样。

4.查找第k名

没啥说的,和其它的一样。

5.查找前驱

没啥说的,和其它的一样。

6.查找后继

没啥说的,和其它的一样。

然后我们会了这道题的所有操作,接下来看看代码吧。我最喜欢看代码了。

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<cstdlib>  
using namespace std;//重点讲一下splay和dele  
struct Tree{  
    int x,fa,son[2],num,size;  
}b[100005];  

int root,m,cnt,hack;  

void update(int u)//更新父节点的size,在旋转时修改会非常蛋疼,不如转完后直接更新  
{  
    b[u].size=b[b[u].son[0]].size+b[b[u].son[1]].size+b[u].num;  
}  

void rotate(int x,int &k)//旋转操作  
{  
    int y=b[x].fa,z=b[y].fa,l,r;  
    if(b[y].son[0]==x) l=0;else l=1;r=l^1;//考虑一下,是对称的,所以只用写一种情况,然后假装另一种情况也是x为左儿子  
    if(y==k&&k==root) k=x,b[x].fa=0;//如果y是根节点,把根节点赋值为x  
    else//更改爷爷的儿子指向  
    {  
        if(b[z].son[1]==y) b[z].son[1]=x;  
        else b[z].son[0]=x;  
        b[x].fa=z;  
    }  
    b[y].son[l]=b[x].son[r];//画图可知这样两个赋值完成翻转  
    b[x].son[r]=y;  
    b[b[y].son[l]].fa=y;  
    b[b[x].son[r]].fa=x;  
    update(y);  
}  

void splay(int x,int &k)  
{  
    int y,z;//y是爸爸,z是儿子  
    while(x!=k)//如果目标点是x,就退出循环  
    {  
        y=b[x].fa;z=b[y].fa;  
        if(y!=k)//如果目标点是爸爸,就只用转x  
        {  
            if((b[z].son[0]==y)^(b[y].son[0]==x)) rotate(x,k);//如果是弯的就转x  
            else rotate(y,k);//直的转y  
        }  
        rotate(x,k);  
    }  
    update(x);//旋转完成后更新x  
}  

void insert(int &u,int x,int las)  
{  
    if(u==0)  
    {  
        u=++cnt;  
        b[u].size++;  
        b[u].x=x;  
        b[u].num=1;  
        b[u].fa=las;  
        splay(u,root);  
        return;  
    }  
    b[u].size++;  
    if(x==b[u].x) b[u].num++,splay(u,root);  
    else if(x<b[u].x)  insert(b[u].son[0],x,u);  
    else  insert(b[u].son[1],x,u);  
}  

void dele(int u,int x)  
{  
    b[u].size--;  
    if(x==b[u].x)  
    {  
        b[u].num--;  
        splay(u,root);  
        if(b[u].num==0)//如果它被删没了  
        {  
            int lson=b[u].son[0];//左儿子  
            if(lson==0) root=b[u].son[1],b[root].fa=0;//没有左儿子,把根变成右儿子好了  
            else//不如把前驱转到根  
            {  
                while(b[lson].son[1]!=0)lson=b[lson].son[1];  
                splay(lson,root);  
                b[root].son[1]=b[u].son[1];//前面的图可得把要删点的右儿子连在根的右儿子即可。  
                b[b[root].son[1]].fa=root;  
            }  
        }  
        return;  
    }  
    if(x>b[u].x) dele(b[u].son[1],x);  
    else dele(b[u].son[0],x);  
}  

int rankth(int u,int x)  
{  
    if(u==0) return 1;  
    if(b[u].x==x)  
    {  
      hack=b[u].num;  
      return b[b[u].son[0]].size+1;  
    }  
    if(x>b[u].x) return rankth(b[u].son[1],x)+b[b[u].son[0]].size+b[u].num;  
    if(x<b[u].x) return rankth(b[u].son[0],x);  
}  

int findit(int u,int k)  
{  
    if(k>b[b[u].son[0]].size&&k<=b[u].num+b[b[u].son[0]].size)  
      return b[u].x;  
    else if(k<=b[b[u].son[0]].size)  
      return findit(b[u].son[0],k);  
    else  
      return findit(b[u].son[1],k-b[u].num-b[b[u].son[0]].size);  
}  

int main()  
{  
    scanf("%d",&m);  
    for(int i=1;i<=m;i++)  
    {  
        int x,q;  
        scanf("%d%d",&x,&q);  
        if(x==1) insert(root,q,0);  
        if(x==2) dele(root,q);  
        if(x==3) printf("%d\n",rankth(root,q));  
        if(x==4) printf("%d\n",findit(root,q));  
        if(x==5)  
        {  
            hack=0;q=rankth(root,q);  
            printf("%d\n",findit(root,q-1));  
        }  
        if(x==6)  
        {  
            hack=0;q=rankth(root,q);  
            printf("%d\n",findit(root,q+hack));  
        }  
    }  
}

我们A掉了一道板子题,然后再来看一道板子题:P3391 【模板】文艺平衡树(Splay)
嗯?这道题和 splay s p l a y 有什么关系么?我咋不会呢?

如果你连这个板子都不会的话,就退赛吧

额,主要是因为我们前面一直在讨论排序二叉树,所以突然不是排序二叉树了就有点不习惯。实际上这确实是一个板子题。不如再理解一下开头说的那段话。嗯,现在会了吧。

???

其实在这里 splay s p l a y 维护的是一段数列顺序。思考一下,不管 splay s p l a y 怎么转,这棵树的中序遍历是不会变的。诶,是不是明白了什么呢?

思考一下,如果要翻转一段区间,把这段区间的所有根节点的左右儿子交换,好像就完成翻转了耶!(画个图)

这里写图片描述

自然,每次修改把所有根都交换显然复杂度爆炸。

写过线段树么?我们在一个节点打一个标记,可以表示这棵子树要翻转,下次遍历的时候翻转就是了,复杂度是 logn l o g n 的。

不对啊?要是我要旋转的区间不是在一棵子树里呢?

啪!

所以这是一个splay的板子题啊, splay s p l a y 就可以实现把一段区间变成一棵子树。至于怎么做。。。(举个栗子,我最喜欢举栗子了)

假设我们要旋转1-3,可以把第0个数转到根节点,然后把第4个转到根节点的右儿子,然后这段区间变成了根的右儿子的左儿子。嗯对,根的右儿子的左儿子。

不对啊,你哪里来的第0个数?

啪!

不如我们先插入一个0和一个 n n +1,然后转1-3,就只用转第1和第5。l和r+2

0 123 4 56

不如贴一波代码:

#include<iostream>  
#include<cstdio>  
#include<cstring>  
using namespace std;  
struct Tree{  
    int son[2],size,fa,x;  
    bool t;  
}b[100005];  

int n,m,root,cnt;  

void pushdown(int u)  
{  
    if(b[u].t==0)return;  
    swap(b[u].son[0],b[u].son[1]);  
    b[u].t=0;b[b[u].son[0]].t^=1;b[b[u].son[1]].t^=1;  
}  

void update(int u)  
{  
    b[u].size=b[b[u].son[0]].size+b[b[u].son[1]].size+1;  
}  

void rotate(int x,int &u)  
{  
    int y=b[x].fa,z=b[y].fa,l,r;  
    if(y==root) root=x,b[x].fa=0;  
    else  
    {  
        b[x].fa=z;  
        if(b[z].son[0]==y) b[z].son[0]=x;  
        else b[z].son[1]=x;  
    }  
    if(b[y].son[0]==x) l=0;else l=1;r=l^1;  
    b[y].son[l]=b[x].son[r];b[x].son[r]=y;  
    b[b[y].son[l]].fa=y;b[b[x].son[r]].fa=x;  
    update(y);  
}  

void splay(int x,int &u)  
{  
    int y,z;  
    while(x!=u)  
    {  
        y=b[x].fa;z=b[y].fa;  
        if(y!=u)  
        {  
            if((b[z].son[0]==y)^(b[y].son[0]==x)==0) rotate(y,u);  
            else rotate(x,u);   
        }  
        rotate(x,u);  
    }  
    update(x);  
}  

void build(int &u,int l,int r,int las)  
{  
    u=++cnt;  
    int mid=(l+r)/2;  
    b[u].size=r-l+1;  
    b[u].x=mid;  
    b[u].fa=las;  
    if(l<mid) build(b[u].son[0],l,mid-1,u);  
    if(r>mid) build(b[u].son[1],mid+1,r,u);  
}  

void check(int u)  
{  
    if(u==0)return;  
    pushdown(u);  
    check(b[u].son[0]);  
    if(b[u].x!=0&&b[u].x!=n+1)cout<<b[u].x<<" ";   
    check(b[u].son[1]);  
}  

void findit(int u,int x,int type)  
{  
    pushdown(u);  
    if(x==b[b[u].son[0]].size+1)  
    {  
        if(type==0)  splay(u,root);  
        else splay(u,b[root].son[1]);  
        return;  
    }  
    else if(x<=b[b[u].son[0]].size)  
      findit(b[u].son[0],x,type);  
    else  
      findit(b[u].son[1],x-1-b[b[u].son[0]].size,type);  
}  

int main()  
{  
    scanf("%d%d",&n,&m);  
    build(root,0,n+1,0);  
    for(int i=1;i<=m;i++)  
    {  
        int l,r;  
        scanf("%d%d",&l,&r);  
        findit(root,l,0);  
        findit(root,r+2,1);  
        b[b[b[root].son[1]].son[0]].t^=1;  
    }  
    check(root);  
}  

由于没有删除操作,这道题就清真多了。记得输出打空格,还有不要把0和n+1输出来了。
我们又A掉了一道板子题,接着我们挖一个坑:P2042 [NOI2005]维护数列





平衡树——treap概述

treap分为两种,一种是普通旋转treap,还有一种是非旋treap。我们还是两种都写一下:

1.旋转 treap t r e a p P3369 【模板】普通平衡树(Treap/SBT)

据说 treap t r e a p tree t r e e heap h e a p 的合写,顾名思义,我们把平衡树和堆合起来就可以得到 treap t r e a p 。其实没有什么思想,首先我们发现在数据随机的情况下排序二叉树的复杂度其实是很优秀的,只有某些数据(比如从小到大插入)会被卡。那我们要做的就很简单了,就是强行把数据变成随机的!!!听起来很暴力是不是?不过代码实现是非常简单的。

我们对于每一个节点 rand r a n d 一个值,我们在满足排序二叉树的性质下使 rand r a n d 值满足堆的性质。所以我们在每次插入新建节点时判断它的 rand r a n d 值与父亲 rand r a n d 值的关系,如果不满足堆就旋转一下。神奇的是,旋转只会改变这两个点的上下关系,所以不会破坏以前其它点满足堆的性质。

那么很容易脑补出代码, rand r a n d 很好写,旋转只需要把 splay s p l a y rotate r o t a t e 搬过来就好了

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<algorithm>  
#include<cmath>  
#include<ctime>  
using namespace std;  
struct Tree{  
    int x,size,num,rand,fa,son[2];  
}b[100005];  
int n,cnt;  
int root,r;  
int hack;  

void check(int u)  
{  
    if(b[u].son[0]!=0)check(b[u].son[0]);  
    cout<<b[u].x<<" "<<b[u].num<<" "<<b[u].size<<endl;  
    if(b[u].son[1]!=0)check(b[u].son[1]);  
}  

void update(int u)  
{  
    b[u].size=b[b[u].son[0]].size+b[b[u].son[1]].size+b[u].num;  
}  

void rotate(int x)  
{  
    int z,y,l,r;  
    while(y=b[x].fa,z=b[y].fa,y!=0&&b[y].rand<b[x].rand)  
    {  
        if(root==y) root=x,b[x].fa=root;  
        else{  
            if(b[z].son[0]==y) b[z].son[0]=x;  
            else b[z].son[1]=x;  
            b[x].fa=z;  
        }  
        if(b[y].son[0]==x) l=0;else l=1;r=l^1;  
        b[y].son[l]=b[x].son[r];b[b[y].son[l]].fa=y;  
        b[x].son[r]=y;b[b[x].son[r]].fa=x;  
        update(y);  
    }  
    update(x);  
}  

void insert(int &u,int x,int las)  
{  
    if(u==0)  
    {  
        u=++cnt;  
        b[u].x=x;  
        b[u].rand=rand()+1;  
        b[u].fa=las;  
    }  
    b[u].size++;  
    if(b[u].x==x) b[u].num++,rotate(u);  
    else if(x>b[u].x) insert(b[u].son[1],x,las);  
    else if(x<b[u].x) insert(b[u].son[0],x,las);  
}  

void dele(int u,int x)  
{  
    b[u].size--;  
    if(b[u].x==x)  
      b[u].num--;  
    else if(b[u].x<x)  
      dele(b[u].son[1],x);  
    else if(b[u].x>x)  
      dele(b[u].son[0],x);  
}  

int rankth(int u,int x)  
{  
    if(u==0) return 1;  
    if(b[u].x==x)  
    {  
        hack=b[u].num;  
        return b[b[u].son[0]].size+1;  
    }  
    if(b[u].x>x)  
      return rankth(b[u].son[0],x);  
    if(b[u].x<x)  
      return rankth(b[u].son[1],x)+b[b[u].son[0]].size+b[u].num;  
}  

int findit(int u,int x)  
{  
    if(x>b[b[u].son[0]].size&&x<=b[b[u].son[0]].size+b[u].num)  
      return b[u].x;  
    else if(x<=b[b[u].son[0]].size)  
      return findit(b[u].son[0],x);  
    else  
      return findit(b[u].son[1],x-b[b[u].son[0]].size-b[u].num);  
}  

int main()  
{  
    srand(time(0));  
    scanf("%d",&n);  
    for(int i=1;i<=n;i++)  
    {  
        int x,k;  
        scanf("%d%d",&x,&k);  
        if(x==1)  insert(root,k,0);  
        if(x==2)  dele(root,k);  
        if(x==3)  printf("%d\n",rankth(root,k));  
        if(x==4)  printf("%d\n",findit(root,k));  
        if(x==5)  
        {  
            hack=0;int q=rankth(root,k);  
            printf("%d\n",findit(root,q-1));  
        }  
        if(x==6)  
        {  
            hack=0;int q=rankth(root,k);  
            printf("%d\n",findit(root,q+hack));  
        }  
    }  
}  

说了那么多,似乎 treap t r e a p 没有什么卵用,什么操作都不好套,据说可以不学。
2.非旋 treap t r e a p P3391 【模板】文艺平衡树(Splay)

这个重点讲一下,以备以后某天可持久化,树套树什么的。非旋 treap t r e a p 哈,非旋,不旋转。这虽然是一个 splay s p l a y 的板子,但是我们可以用 treap t r e a p 的板子过。非旋 treap t r e a p 支持以下几种操作:

1. split s p l i t :把一棵平衡树前 k k 大的拆一棵树出来,然后剩下的是一棵树,也就是拆成两棵树

2.merge: 把两颗平衡树合并在一起,但是要保证前一棵平衡树严格大于后一棵(所有节点都大于)。这样就发现非旋 treap t r e a p 的插入操作十分蛋疼

考虑 splay s p l a y 是怎么做这道题的,把一棵子树提出来,然后打一个旋转标记, pushdown p u s h d o w n 啥的,慢慢维护一下就行。

相同的,我们要反转一个区间的话, split s p l i t 两次( l1 l − 1 r r <script type="math/tex" id="MathJax-Element-302">r</script>)。然后中间那棵树就是需要翻转的的区间,然后在这棵树上打一个标记,再合并平衡树就好了。

直接贴代码,看一下这两个操作是怎么写的:

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<ctime>  
#include<cstdlib>  
using namespace std;  
struct Tree{  
    int lson,rson,val,x,size;//val是rand值  
    bool t;//t表示是否翻转  
}b[100005];  
int n,m,cnt,root;  

void pushdown(int u)  
{  
    if(b[u].t==0) return;  
    b[u].t=0;//记得清零,被坑了好多次  
    b[b[u].lson].t^=1;b[b[u].rson].t^=1;  
    swap(b[u].lson,b[u].rson);  
}  

void update(int u)  
{  
    b[u].size=b[b[u].lson].size+b[b[u].rson].size+1;  
}  

void check(int u)  
{  
    if(u==0) return;  
    pushdown(u);  
    check(b[u].lson);  
    printf("%d ",b[u].x);  
    check(b[u].rson);  
}  

void split(int &x,int k,int &y)//划分树,x表示划分后前k个的根,y表示其它的的根  
{  
    pushdown(x);//下传x,不用下传y,y只是记录根编号,不是实际递归的  
    if(b[x].size==k){y=0;return;}//如果整棵树正好大小为k,前k个的根就是x,y为0  
    if(k<=b[b[x].lson].size)//如果左儿子大小大于k  
    {  
        split(b[x].lson,k,y);//分给左儿子  
        swap(b[x].lson,y);//把y接到x的左儿子上,现在x表示除去前k个的平衡树,y表示前k个的平衡树  
        swap(x,y);//x,y,表示的正好反了,交换一下  
        update(y);//更新y(由于这几步操作没有改动x及它的儿子信息,所以不用update)  
    }  
    else  
    {  
        split(b[x].rson,k-b[b[x].lson].size-1,y);//分给右儿子  
        update(x);//同理没有修改y,不需要update  
    }  
}  

int merge(int x,int y)//合并操作,x,y分别为需要合并平衡树的根(保证x严格小于y),返回合并后的新根。  
{  
    if(y==0||x==0) return x+y;//如果,某一平衡树为空就结束合并,return  
    pushdown(x);pushdown(y);//下传标记  
    if(b[x].val>=b[y].val){b[x].rson=merge(b[x].rson,y);update(x);return x;}//只有可能y是x的右儿子,或者x是y的左儿子,哪一个的rand值小哪一个作为根,递归合并  
    if(b[x].val<b[y].val){b[y].lson=merge(x,b[y].lson);update(y);return y;}//注意一定要把小的从左边传入  
}  

void insert(int x)  
{  
    b[++cnt].val=rand();  
    b[cnt].x=x;b[cnt].size=1;  
    root=merge(root,cnt);  
}  

int main()  
{  
    srand(time(0));  
    scanf("%d%d",&n,&m);  
    for(int i=1;i<=n;i++)  
      insert(i);  
    for(int i=1;i<=m;i++)  
    {  
        int l,r,r1,r2;  
        scanf("%d%d",&l,&r);  
        split(root,r,r1);  
        split(root,l-1,r2);  
        b[r2].t^=1;  
        root=merge(root,r2);  
        root=merge(root,r1);  
    }  
    check(root);  
}  
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
malloc lab平衡树是一个在C语言中实现的数据结构,其目的是通过动态内存分配函数malloc来构建一个平衡二叉树。 平衡树,是指左右子树的高度差最多为1的二叉排序树。为了保持树的平衡,我们需要在插入和删除节点时进行相应的旋转操作。由于malloc函数的特性是分配一块连续的内存块,我们可以利用这一特性来构建平衡树。 首先,我们需要定义一个节点结构体,其中包含左右子节点指针、键值和平衡因子等信息。然后,我们通过malloc函数动态分配一个节点并初始化,将其插入到平衡树中。 在插入节点时,我们需要根据节点的键值和当前根节点的键值比较来确定插入的位置。如果键值小于当前节点的键值,则将其插入到当前节点的左子树中;如果键值大于当前节点的键值,则将其插入到当前节点的右子树中。然后,我们需要更新插入路径上各个节点的平衡因子,并进行相应的旋转操作来调整树的平衡性。 在删除节点时,我们也需要进行相应的旋转操作来保持树的平衡性。首先,我们需要找到要删除的节点,并进行删除操作。然后,我们需要根据删除路径上各个节点的平衡因子来进行旋转操作。 通过动态内存分配函数malloc,我们可以灵活地构建和调整平衡树。但需要注意的是,使用malloc函数分配的内存需要在使用后进行释放,以避免内存泄漏问题。因此,在删除节点后,我们需要使用free函数释放相应的内存空间。 总而言之,malloc lab平衡树是一种利用动态内存分配函数malloc来构建的平衡二叉树,通过插入和删除节点,并进行相应的旋转操作来保持树的平衡性。这是一种高效的数据结构,可以在C语言中灵活地使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值