• 文艺平衡树-splay的区间操作


      真的是个神题,蒟蒻表示无力吐槽。刚开始以为是一个板子题,看着题解打了一遍,大概也理解了他是怎么实现的,然后我就去做别的题了,然后就在Three_D大佬的询问下蒙*了。最后还是问的nc哥,并思考了一个中午才搞明白。最主要的一点是,旋转不会改变树的中序遍历。

    【建树操作】

      对于一棵BST,区间[l,r],如果把l-1 splay到根,把r+1 splay到根的右子树,那么[l,r]即为根的右子树的左子树,如果不是BST,这个性质同样适用。然后因为旋转可能会涉及到1n,所以要建立1n+2两个哨兵节点;因为平衡树维护的是一个序列,所以一开始树的中序遍历应该是原序列:

     1 rt=build_tree(0,1,n+2);
     2 int build_tree(int fa,int l,int r)
     3 {
     4     if(l>r) return 0;
     5     int mid=(l+r)>>1,
     6         now=++sz;
     7     key[now]=data[mid];f[now]=fa;tag[now]=0;
     8     ch[now][0]=build_tree(now,l,mid-1);
     9     ch[now][1]=build_tree(now,mid+1,r);
    10     pushup(now);
    11     return now;
    12 }
    View Code

    splay

      rotate不改变树的中序遍历,原rotate函数不变。因为要将r+2 splay到根的右节点而不是根,所以要多传一个参:

    1 void splay(int x,int goal)
    2 {
    3     for(int fa;(fa=f[x])!=goal;rotate(x))
    4         if(f[fa]!=goal)        
    5             rotate(get(fa)==get(x)?fa:x);
    6     if(!goal)rt=x;
    7 }
    View Code

    【翻转区间】*

      对于区间[l,r],l splay到根,r+2 splay到根的右儿子,那么r+2的左子树就是[l+1,r+1].如果将r+2的左儿子的左右子树翻转,并递归地翻转下去,那么[l+1r+1]的中序遍历就会翻转。但是这样的时间复杂度太高,所以延续线段树中懒标记的操作。

    1 void turn(int l,int r)
    2 {
    3     l=rnk(l);
    4     r=rnk(r+2);
    5     splay(l,0);
    6     splay(r,l);
    7     pushdown(rt);
    8     tag[ch[ch[rt][1]][0]]^=1;
    9 }
    View Code

      然后说说把我弄懵逼的东西,对于序列 [12345],平衡树如图:

      绿色的数字为key值,一定要注意区分节点的排名和这个节点的值,在建树时,key[now]=data[mid];当前节点的key存储的是序列的值,而图中黑色的数字是节点的排名,可能有点难以理解,那举个例子:

      对于[12345],如果要翻转区间[2,3],那么在平衡树中找到排名为2的数(l=rnk(l)),即2,将他旋转到根,找到排名为5的数(r=rnk(r+2)),即5,将他旋转到根的右儿子,则5的左子树为[34],key值就是所要翻转的区间[23],打上标记,翻转完成。

    Ps.除区间翻转外,还可以进行区间删除,区间加上x等操作。

    标记下传】

      每到一个节点就要下传懒标记:

     1 void pushdown(int x)
     2 {
     3     if(x && tag[x])
     4     {
     5         tag[ch[x][0]]=tag[ch[x][0]]^1;
     6         tag[ch[x][1]]=tag[ch[x][1]]^1;
     7         swap(ch[x][0],ch[x][1]);
     8         tag[x]=0;
     9     }
    10 }
    View Code

    【输出结果】

      中序遍历整棵树即可:

    1 void write(int now)
    2 {
    3     pushdown(now);
    4     if(ch[now][0])write(ch[now][0]);
    5     if(key[now]!=INF && key[now]!=-INF)cout<<key[now]<<" ";
    6     if(ch[now][1]) write(ch[now][1]);
    7 }
    View Code

    【完整代码

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cmath>
     4 #define INF 0x7fffffff
     5 using namespace std;
     6 int n,m,data[1000000];
     7 int ch[1000000][2],size[1000000],cnt[1000000],f[1000000],key[1000000],tag[1000000],sz,rt;
     8 
     9 int get(int x){return ch[f[x]][1]==x;}
    10 void pushup(int x)
    11 {
    12     size[x]=size[ch[x][0]]+size[ch[x][1]]+1;
    13 }
    14 void pushdown(int x)
    15 {
    16     if(x && tag[x])
    17     {
    18         tag[ch[x][0]]=tag[ch[x][0]]^1;
    19         tag[ch[x][1]]=tag[ch[x][1]]^1;
    20         swap(ch[x][0],ch[x][1]);
    21         tag[x]=0;
    22     }
    23 }
    24 int build_tree(int fa,int l,int r)
    25 {
    26     if(l>r) return 0;
    27     int mid=(l+r)>>1,
    28         now=++sz;
    29     key[now]=data[mid];f[now]=fa;tag[now]=0;
    30     ch[now][0]=build_tree(now,l,mid-1);
    31     ch[now][1]=build_tree(now,mid+1,r);
    32     pushup(now);
    33     return now;
    34 }
    35 void rotate(int x)
    36 {
    37     int old=f[x],oldf=f[old],which=get(x);
    38     pushdown(oldf),pushdown(old),pushdown(x);
    39     ch[old][which]=ch[x][which^1];f[ch[old][which]]=old;
    40     ch[x][which^1]=old;f[old]=x;
    41     f[x]=oldf;
    42     if(oldf) ch[oldf][ch[oldf][1]==old]=x;
    43     pushup(old),pushup(x);
    44 }
    45 void splay(int x,int goal)
    46 {
    47     for(int fa;(fa=f[x])!=goal;rotate(x))
    48         if(f[fa]!=goal)        
    49             rotate(get(fa)==get(x)?fa:x);
    50     if(!goal)rt=x;
    51 }
    52 int rnk(int x)
    53 {
    54     int now=rt;
    55     while(1)
    56     {
    57         pushdown(now);
    58         if(x<=size[ch[now][0]])now=ch[now][0];
    59         else
    60         {
    61             x-=size[ch[now][0]]+1;
    62             if(!x)return now;
    63             now=ch[now][1];
    64         }
    65     }
    66 }
    67 void turn(int l,int r)
    68 {
    69     l=rnk(l);
    70     r=rnk(r+2);
    71     splay(l,0);
    72     splay(r,l);
    73     pushdown(rt);
    74     tag[ch[ch[rt][1]][0]]^=1;
    75 }
    76 void write(int now)
    77 {
    78     pushdown(now);
    79     if(ch[now][0])write(ch[now][0]);
    80     if(key[now]!=INF && key[now]!=-INF)cout<<key[now]<<" ";
    81     if(ch[now][1]) write(ch[now][1]);
    82 }
    83 signed main()
    84 {
    85     cin>>n>>m;
    86     for(int i=1;i<=n;i++)data[i+1]=i;
    87     data[1]=-INF,data[n+2]=INF;
    88     rt=build_tree(0,1,n+2);
    89     int x,y;
    90     for(int i=1;i<=m;i++)
    91     {
    92         cin>>x>>y;
    93         turn(x,y);
    94     }
    95     write(rt);
    96     return 0;
    97 }
    View Code
    波澜前,面不惊。
  • 相关阅读:
    样式问题
    布局
    通用模板实现可变参数函数
    使用单例模式实习string类
    动态生成一维数组和二维数组
    自定义的类传数据到主窗口控件上的方法
    使用TableView
    G480折腾上了黑苹果,完美了,哈哈
    error C2383: 此符号中不允许有默认参数
    动态链接库的隐式动态链接和显示动态链接
  • 原文地址:https://www.cnblogs.com/Al-Ca/p/11017901.html
Copyright © 2020-2023  润新知