• [洛谷P3391] 文艺平衡树 (Splay模板)


    初识splay

    学splay有一段时间了,一直没写......

    本题是splay模板题,维护一个1~n的序列,支持区间翻转(比如1 2 3 4 5 6变成1 2 3 6 5 4),最后输出结果序列。

    模板题嘛......主要了解一下splay的基本操作QwQ

    原题地址链接[洛谷P3391文艺平衡树]

    1.基本概念

    splay是一种二叉搜索树,节点的权值满足lson<p<rson,故可以像其他二叉搜索树一样在树上二分查找某数排名,排名为k的数,以及前驱后继等。

    普通的二叉搜索树在面对特殊数据时树的深度会从log n退化成接近n(退化成链),这样操作的时间复杂度会从O(log n)退化成O(n),影响效率。

    splay通过旋转维持树的平衡。这个操作后面会提到。

    2.基本操作

    二叉搜索树的基本操作:求排名为k的数。

     1 int rank(int p,int k)
     2 {
     3     pushdown(p);
     4     if(k<=sz[s[p][0]])
     5         return rank(s[p][0],k);
     6     else if(k==sz[s[p][0]]+1)
     7         return p;
     8     else
     9         return rank(s[p][1],k-sz[s[p][0]]-1);
    10 }

    简单的树上二分。

    3.核心操作:splay

    splay的精髓在于骚气的旋转。(名字就是这么来的哈哈哈~)

    splay的核心操作是splay(一脸懵逼),splay(x,y)意为通过一系列旋转,将点x旋转到点y下面,使x成为y的儿子。

    每次旋转通过rotate函数实现:

     1 void rotate(int p)
     2 {
     3     int fa=f[p];
     4     bool k=id(p);
     5     s[fa][k]=s[p][!k];
     6     s[p][!k]=fa;
     7     s[f[fa]][id(fa)]=p;
     8     f[p]=f[fa];
     9     f[s[fa][k]]=fa;
    10     f[fa]=p;
    11     refresh(fa);
    12     refresh(p);
    13 }

    rotate的时候严格满足splay二叉搜索树的性质:lson<p<rson。

    将p提到fa的位置,根据大小关系决定fa是作为p的左儿子还是右儿子,这样实际上是fa挤掉了p原先的某个儿子,而p转上去,让出了fa的一个儿子的位置。

    所以最后让那个被fa挤掉的p的孤儿作为fa的某个儿子,填到空缺的地方去(原来p的位置)。

    至于splay的实现方法...有两种:单旋和双旋。

    单旋即无脑地一直转,直到把x转到y下面。

    1 void splay(int p,int g)  // 单旋
    2 {
    3     while(f[p]!=g)rotate(p);
    4     if(!g)root=p;
    5 }

    比起单旋,双旋能更好的维护splay的平衡。

     1 void splay(int p,int g) // 双旋
     2 {
     3     while(f[p]!=g)
     4     {
     5         int fa=f[p];
     6         if(f[fa]==g)
     7         {
     8             rotate(p);
     9             break;
    10         }
    11         if(id(p)^id(fa))rotate(p);
    12         else rotate(fa);
    13         rotate(p);
    14     }
    15     if(!g)root=p;
    16 }

    利用splay操作,我们就可以用这棵树实现很多其它平衡树实现不了的功能。

    4.元素的插入、删除、查询及修改

    设x为 要插入的/要删除的/要查询的/要修改的 元素or区间。

    进行这些操作之前,运用旋转操作把x的前驱pre转到根位置,把x的后继post转到根的下面,post>pre,所以此时post一定是pre的右儿子。

    (如果是区间,pre就是left的前驱,post就是right的后继)

    如图:

    此时,根据二叉搜索树的性质,要删除/查询/修改的元素or区间就一定在post的左子树那里。如图:(目标子树:红色部分)

    4.1 插入

    如果是插入,红色部分一定为空,在那里插入即可。

    4.2 删除

    残忍抛弃红色部分。

    4.3 查询

    在红色部分查询。

    4.4 修改

    在这道题里是区间翻转。

    我们并不需要真的翻转,打个标记就行。

    标记需要下传的时候,交换左右子树的左右子树,在左右儿子上打标记,清掉自身标记。

    1 void pushdown(int p)
    2 {
    3     if(!fl[p])return;
    4     fl[s[p][0]]^=1;
    5     fl[s[p][1]]^=1;
    6     swap(s[s[p][0]][0],s[s[p][0]][1]);
    7     swap(s[s[p][1]][0],s[s[p][1]][1]);
    8     fl[p]=0;
    9 }

    这样就行了。

    完事了?

    完事了。

    最后二分输出序列即可。

    其他细节见代码。

      1 #include<cstdio>
      2 #include<cstring>
      3 #include<algorithm>
      4 #define N 100005
      5 #define id(x) (s[f[x]][1]==x) // 判断是左儿子还是右儿子
      6 using namespace std;
      7 
      8 int f[N],s[N][2],val[N],sz[N],root,tot; // 分别是父亲,儿子,值,子树大小,树根,元素数量
      9 bool fl[N]; // 翻转标记
     10 
     11 void refresh(int p) // 更新size
     12 {
     13     sz[p]=sz[s[p][0]]+sz[s[p][1]]+1;
     14 }
     15 
     16 void pushdown(int p) // 下传标记
     17 {
     18     if(!fl[p])return;
     19     fl[s[p][0]]^=1;
     20     fl[s[p][1]]^=1;
     21     swap(s[s[p][0]][0],s[s[p][0]][1]);
     22     swap(s[s[p][1]][0],s[s[p][1]][1]);
     23     fl[p]=0;
     24 }
     25 
     26 void rotate(int p) // 把p转上去
     27 {
     28     int fa=f[p];
     29     bool k=id(p);
     30     s[fa][k]=s[p][!k];
     31     s[p][!k]=fa;
     32     s[f[fa]][id(fa)]=p;
     33     f[p]=f[fa];
     34     f[s[fa][k]]=fa;
     35     f[fa]=p;
     36     refresh(fa);
     37     refresh(p);
     38 }
     39 /*
     40 void splay(int p,int g) // 单旋
     41 {
     42     while(f[p]!=g)rotate(p);
     43     if(!g)root=p;
     44 }
     45 */
     46 void splay(int p,int g) // 双旋
     47 {
     48     while(f[p]!=g)
     49     {
     50         int fa=f[p];
     51         if(f[fa]==g)
     52         {
     53             rotate(p);
     54             break;
     55         }
     56         if(id(p)^id(fa))rotate(p);
     57         else rotate(fa);
     58         rotate(p);
     59     }
     60     if(!g)root=p;
     61 }
     62 
     63 int rank(int p,int k) // 查询rank为k的元素
     64 {
     65     pushdown(p);
     66     if(k<=sz[s[p][0]])
     67         return rank(s[p][0],k);
     68     else if(k==sz[s[p][0]]+1)
     69         return p;
     70     else
     71         return rank(s[p][1],k-sz[s[p][0]]-1);
     72 }
     73 
     74 int build(int l,int r,int fa) // 建树  实际上一个一个插入也行,但是这样二分建树可以使初始树更平衡
     75 {
     76     if(l>r)return 0;
     77     int mid=(l+r)>>1;
     78     int p=++tot;
     79     s[p][0]=build(l,mid-1,p);
     80     s[p][1]=build(mid+1,r,p);
     81     val[p]=mid;
     82     f[p]=fa;
     83     refresh(p);
     84     return p;
     85 }
     86 
     87 void change(int l,int r) // 区间翻转
     88 {
     89     int pre,post,rt;
     90     pre=rank(root,l-1);
     91     splay(pre,0);
     92     post=rank(root,r+1);
     93     splay(post,pre);
     94     rt=s[post][0];
     95     swap(s[rt][0],s[rt][1]);
     96     fl[rt]^=1;
     97 }
     98 
     99 void print(int p) // 二分输出结果序列
    100 {
    101     if(!p)return;
    102     pushdown(p);
    103     print(s[p][0]);
    104     printf("%d ",val[p]);
    105     print(s[p][1]);
    106 }
    107 
    108 int n,m;
    109 
    110 int main()
    111 {
    112     scanf("%d%d",&n,&m);
    113     root=build(0,n+1,0);
    114     for(int i=1;i<=m;i++)
    115     {
    116         int lb,rb;
    117         scanf("%d%d",&lb,&rb);
    118         change(lb+1,rb+1);
    119     }
    120     splay(rank(root,1),0);
    121     splay(rank(root,n+2),root);
    122     print(s[s[root][1]][0]);
    123     return 0;
    124 }
    125 
    126 complete code of splay tree
    complete code of splay tree

  • 相关阅读:
    C++总结
    KMP算法
    理解I/O Completion Port(完成端口)
    [转载]Windows网络编程系列教程之四:Select模型
    [转载]理解 I/O Completion Port (IOCP完成端口)
    [转载]IOCP模型的总结
    发个IOCP的C++例子
    说说网络通信模型
    几个网络模型的示例代码(BlockingModel、OverlappedModel、WSAEventSelect、CompletionRoutine)..c++
    关于Socket 多线程 的一篇好文章
  • 原文地址:https://www.cnblogs.com/cervusy/p/9474709.html
Copyright © 2020-2023  润新知