• 平衡树(模板+文艺平衡树)


     模板存一下:求前驱后继,求x的排名和排在的x名的数,删除和插入一个数。

    /*
    https://blog.csdn.net/clove_unique/article/details/50630280
    */
    #include<bits/stdc++.h>
    using namespace std;
    #define N 100005
    #define INF 2100000000
    int f[N],ch[N][5],key[N],siz[N],cnt[N],sz=0,root=0;
    void clear(int x){ ch[x][1]=ch[x][0]=f[x]=cnt[x]=siz[x]=0;key[x]=-INF;} 
    void update(int x)
    {
        if(!x) return ;
        siz[x]=cnt[x];
        if(ch[x][0]) siz[x]+=siz[ch[x][0]];//记得是siz!! 
        if(ch[x][1]) siz[x]+=siz[ch[x][1]];
    }
    int get(int x)//返回x是左儿子还是右儿子 
    {
        return (ch[f[x]][1]==x);
    }
    void ro(int x)//旋转操作 
    {
        int old=f[x],oldf=f[f[x]],wi=get(x);//父亲 父亲的父亲 wi判断是左儿子还是右儿子 
        ch[old][wi]=ch[x][wi^1]; f[ch[old][wi]]=old;//父亲的原儿子接到与x关系相反的儿子 那个儿子自己也将父亲变成它 
        f[old]=x; ch[x][wi^1]=old; //自己变成父亲的父亲 父亲变成方向相反的儿子 
        f[x]=oldf;//自己的父亲变成父亲的父亲 
        if(oldf) ch[oldf][ch[oldf][1]==old]=x;//父亲的父亲的儿子也变成自己 替补到old的位置上去 
        update(old); update(x);//先update深度大的!! 
    }
    
    void splay(int x)
    {
        for(int fa;(fa=f[x]);ro(x))
        if(f[fa])//父亲的父亲要存在 因为要旋到f[fa]的儿子 
         ro((get(x)==get(fa)?fa:x));//如果x与fa在一条直线上就应该先旋fa 
        root=x;//已经把x旋转到根 就应该记录一下!! 
    }
     void add(int v)//插入值为v的点 
    {
        //sz是节点的编号 
        if(root==0) { sz++; ch[sz][0]=ch[sz][1]=f[sz]=0; key[sz]=v; cnt[sz]=1; siz[sz]=1; root=sz; return ; }
        int now=root,fa=0;//从根节点开始找位置 
        while(1)
        {
            if(key[now]==v)//已经有了这个点 就直接在cnt上++(二叉平衡树不能有重复的点) 还要记得旋一下 
            {
                cnt[now]++; update(now); update(fa); splay(now); break;
            }
            fa=now; now=ch[now][key[now]<v];//key[now]<v便于判断是在左边还是右边 
            if(now==0)//新建一个节点 
            {
                sz++; ch[sz][0]=ch[sz][1]=0; f[sz]=fa; cnt[sz]=1; siz[sz]=1; key[sz]=v;
                ch[fa][key[fa]<v]=sz;//将father的左或右儿子赋成sz 
                update(fa); splay(sz);//把新建的节点旋到根 而不是now 
                break;
            }
        }
    }
    int find(int v)//找到值为v的数的排名
    {
        int now=root,ans=0;
        while(1)
        {
            //printf("la:%d ",now);
            if(v<key[now]) now=ch[now][0];
            else
            {
                ans+=(ch[now][0]?siz[ch[now][0]]:0);//如果now的左儿子存在的话就 + 否则不加 
                //find的时候要是splay一下 保证时间复杂度!! 
                if(key[now]==v) { splay(now); return ans+1; }
                ans+=cnt[now]; now=ch[now][1];
            }
        }
    }
    int kth(int x)//找到的第x名的数 
    {
        int now=root;
        while(1)
        {
            if(ch[now][0]&&x<=siz[ch[now][0]]) now=ch[now][0];//随时都要判断是否存在儿子 记得有<=!! 
            else
            {
                int p=(ch[now][0]?siz[ch[now][0]]:0)+cnt[now];//记得考虑它本身的出现次数 
                if(x<=p) return key[now];
                x-=p; now=ch[now][1]; 
            }
        }
    }
    //进行找前驱后继时会先把x旋转到根 所以不用传值进去 
    int q_pre()//前驱是第一个小于x的 利用二叉树的性质 找左子树中最靠右的 
    {
        int now=ch[root][0];
        while(ch[now][1]) now=ch[now][1];////////////////
        return now;
    }
    int q_hou()
    {
        int now=ch[root][1];
        while(ch[now][0]) now=ch[now][0];
        return now;
    }
    void dele(int x)
    {
        int blala=find(x);
        if(cnt[root]>1) { cnt[root]--; update(root); return ; }//个数减少了记得update 
        if(!ch[root][0]&&!ch[root][1]) { clear(root); root=0; return ; }
        if(!ch[root][0]) 
        { int oldrt=root; root=ch[root][1]; f[root]=0; clear(oldrt); return ; }
        else if(!ch[root][1])
        { int oldrt=root; root=ch[root][0]; f[root]=0; clear(oldrt); return ; } 
        //有两个及以上的儿子
        int qian=q_pre(),oldrt=root;
        splay(qian);//将前驱旋到根 
        f[ch[oldrt][1]]=root; ch[root][1]=ch[oldrt][1];//把原根的右子树接在新根上 
        clear(oldrt); update(root);//清理旧根 update新根 
    }
    int main()
    {
        int n,x,op;
        scanf("%d",&n);
        while(n--)
        {
            scanf("%d%d",&op,&x);
            if(op==1) add(x);
            else if(op==2) dele(x);
            else if(op==3) printf("%d
    ",find(x));
            else if(op==4) printf("%d
    ",kth(x));
            else if(op==5) add(x),printf("%d
    ",key[q_pre()]),dele(x);
            else if(op==6) add(x),printf("%d
    ",key[q_hou()]),dele(x); 
        }
        return 0;
    }
    /*
    10
    1 106465
    4 1
    1 317721
    1 460929
    1 644985
    1 84185
    1 89851
    6 81968
    1 492737
    5 493598
    
    10
    1 5
    1 3
    1 6
    1 2
    1 4
    1 7
    3 5
    */
    splay模板

    题意:给一个初始序列,求经过若干次区间翻转后的序列。

    思路:

    维护什么?  用splay维护下标,key值存下标对应的值,如序列:1 3 4 2  则1存的key值是1,2存的key值是3,3存的key值是4,4存的key值是2.

    怎么翻转?  当要翻转l和r的时候,先找到l和r这两个下标对应splay中的编号(sply节点的编号),然后将l-1旋转到根,r+1旋转到根的右儿子,由于中序遍历的有序性,[l,r]这个区间就在r+1的左子树,如此操作就实现了区间的提取。提取区间后,先对其打上一个翻转标记,当需要查询是,将翻转标记下传,一层一层地将左右子树翻转,实现区间翻转。

    为什么翻转之后找rank还能找对?   这里的rank函数其实是找到下标对应splay的节点编号,而下标的中序遍历是有序的,那么通过siz的比较就可以找到对应位置

    而这道题的下标和值域对应的初始值是一样的,不要弄混了。

    /*
    洛谷P3391 【模板】文艺平衡树Splay
    题意:求区间翻转后的序列
    思路:把l-1旋到根 r+1旋到l-1的右儿子 这样r+1的左儿子就是l到r这段区间 实现了区间的提取
    splay的性质:中序遍历一直是有序的 所以区间的翻转就可以 
    */
    #include<bits/stdc++.h>
    using namespace std;
    #define inf 0x7f7f7f
    #define N 100005//数组不用加倍 因为平衡树一个点对应一个点 而不是一段区间 
    #define mid ((l+r)>>1)
    int ch[N][3],siz[N],key[N],fl[N],a[N],ndnum=0,root=0,fa[N];
    void update(int s){ siz[s]=siz[ch[s][0]]+siz[ch[s][1]]+1; }//+1因为有它自己 
    int build(int f,int l,int r)
    {
        if(l>r) return 0;
        int s=++ndnum;
        key[s]=a[mid]; fa[s]=f; fl[s]=0;
        ch[s][0]=build(s,l,mid-1);//注意与线段树的差异:s记录的是mid这个点 而它的左右儿子分别记录l~mid-1 mid+1~r 
        ch[s][1]=build(s,mid+1,r);
        update(s);
        return s;
    }
    int get(int x){ return (ch[fa[x]][1]==x) ; }
    void pushdown(int x)
    {
        if(x&&fl[x]){
            fl[ch[x][0]]^=1;//向左右儿子传标记 
            fl[ch[x][1]]^=1;
            swap(ch[x][0],ch[x][1]);//区间翻转 
            fl[x]=0;
        }
    }
    void rotate(int x)
    {
        int old=fa[x],oldf=fa[old],which=get(x);
        ch[old][which]=ch[x][which^1];
        fa[ch[old][which]]=old;
        fa[old]=x; ch[x][which^1]=old;
        fa[x]=oldf;
        if(oldf) ch[oldf][ch[oldf][1]==old]=x;
        update(old),update(x);
    }
    void splay(int x,int tt)//将x旋到tt的儿子节点 
    {
        //其实这个操作旋了两次 一次是for结束时 一次是if判断后 
        for(int f;(f=fa[x])!=tt;rotate(x))
        //判断旋两次后能不能到tt的儿子节点 如果能到 就不用旋了 直接通过for循环来旋 
        if(fa[fa[x]]!=tt) rotate(get(x)==get(f)?f:x);//如果在一条直线 就要先旋fa 
        if(!tt) root=x;  
    }
    int rank(int x)
    {
        int now=root;
        while(1){
            pushdown(now);//查找排名时记得下传标记 
            if(x<=siz[ch[now][0]]) now=ch[now][0];
            else{
                x=x-siz[ch[now][0]]-1;//与线段树不同的是 它不仅要减去儿子节点 还要减去它自己 因为它自己存了mid这个点 
                if(!x) return now;
                now=ch[now][1];
            }
        }
    }
    void turn(int l,int r)
    {
        int x=rank(l),y=rank(r+2);//因为维护的是值域平衡树 所以要先找到在平衡树中的排位(也就是说对应着哪个点) 
        splay(x,0); splay(y,x);//把x旋到根节点(0是因为根的父亲是0) 把y旋到x的右节点 
        fl[ch[ch[root][1]][0]]^=1;
    }
    void dfs(int now)
    {
        pushdown(now);
        if(ch[now][0]) dfs(ch[now][0]);
        if(key[now]!=-inf&&key[now]!=inf) printf("%d ",key[now]);//注意是key里面存储真正的值 而now只是平衡树的节点 
        if(ch[now][1]) dfs(ch[now][1]);
    }
    int main()
    {
        int n,m,l,r;
        scanf("%d%d",&n,&m);
        for(int i=2;i<=n+1;i++) a[i]=i-1;
        a[1]=-inf; a[n+2]=inf;
        root=build(0,1,n+2);//根是0 注意是1 到 n+2 因为加了正无穷和负无穷 
        while(m--){
            scanf("%d%d",&l,&r);
            turn(l,r);
        }
        dfs(root);
    }
    /*
    5 3
    1 3
    1 3
    1 4
    */
    文艺平衡树
  • 相关阅读:
    02-高阶函数 map filter sorted
    01-切片的赋值操作
    学习资料记录
    django_初级学习(1)
    git配置使用
    openpyxl操作表格(2)
    openpyxl模块操作excell表格(1)
    精简语法
    MySQL常见面试题
    02-图片转字符画
  • 原文地址:https://www.cnblogs.com/mowanying/p/11247033.html
Copyright © 2020-2023  润新知