• 左偏树


    左偏树,是一种可以在 (Theta(log_2 n)) 复杂度内合并的一种数据结构。

    它是以二叉树的形态来维护堆的性质,由此也延伸出很多别的操作。

    接下来本文所述堆及其操作均默认为小根堆。

    []

    一些约定

    • (v_i) 节点 (i) 的权值。

    • (l_i,r_i) 节点 (i) 的左右儿子。

    • (d_i) 节点 (i) 到其最近外节点的距离。

    • (f_i) 节点 (i) 的父节点。

    什么是外节点:一个左儿子或右儿子是空节点的节点。

    []

    左偏树的性质

    堆性质

    作为堆式数据结构显然有堆的性质,即对任意一个树中的点 (i)(v_i<v_{l_i},v_i<v_{r_i})

    但是任意一个节点左右儿子的大小并不确定,这也不在我们的维护范围之内。

    左偏性质

    左偏,就是对于任意一个树中的点 (i)(d_{l_i}>d_{r_i})

    另外的,对于任意一个树中的点 (i)(d_i=d_{r_i}+1)

    []

    左偏树的操作

    合并操作

    左偏树最核心的操作,其余的操作也均以这个操作为基础。

    假设我们要合并分别以 (a,b) 为根节点的两棵左偏树。

    首先,若其中一棵树的根为空节点,则另一棵树的根成为新树的根。

    若两个树根均存在,那么权值更小的必定成为新的树根。

    这里我们假设 (v_a<v_b),即 (a) 为新树的根。为了避免分类讨论,当 (v_a>v_b) 时,我们可以将其交换。

    此时我们再令 (b)(a) 的右儿子进行合并,重新比较其大小。如此不断比较下去直到遇到空节点或叶节点为止。

    这样递归返回每次合并的根节点的值,重置此树的相关节点。

    另外,由于是对右儿子进行合并,所以可能合并后的树不再满足左偏性质。此时我们交换左右子树。

    我们要时刻保证对于任意一个树中的节点 (i)(d_i=d_{r_i}+1),所以我们重新规定一下右儿子的距离即可。

    实现也比较容易。

    int merge(int x,int y){
      if(!x||!y) return x+y;
      if(v[y]<v[x]) swap(x,y);
      rs=merge(rs,y);
      if(dis[ls]<dis[rs])swap(ls,rs);
      dis[x]=dis[rs]+1;
      return x;
    }
    

    还有一种写法是随机合并,引入随机数来判断是否交换左右节点。

    这样做可能会使树不满足左偏性质,但是省去了距离的相关计算,且复杂度不变。

    取最值操作

    由于左偏树的堆性质,它的最值肯定在堆顶。那么就直接不断跳父节点就行了。

    但是我们发现暴跳太慢了。我们可以借助并查集对其路径压缩。

    对于同一棵左偏树中的点用并查集连接到一起,并钦定其祖先节点为堆顶。

    于是就可以在 (Theta(log_2 n)) 的时间内快速找出最值了。

    删除操作

    删除操作只针对堆顶节点来说。

    删除节点需要重置其信息(左右儿子和距离)。

    另外,删除此节点之后,其真正的左右儿子并没有被删除,但是现在他们都没有了父亲(?),所以我们需要合并这两个点。

    fa[lson[x]]=fa[rson[x]]=fa[x]=merge(lson[x],rson[x]);
    

    注意这个节点本身的父亲也要指向合并后的根节点。

    因为路径压缩后删除此节点,此节点的父亲指向的是自己,若不更改可能会导致后面出各种问题。

    插入节点

    等同于将其与一棵大小为一的堆合并。

    此时要保证插入的这个点的各个信息已经处理完善。

    全树加/减/乘一个值

    维护懒标记,从根节点开始不断向下穿即可。具体是在删除/合并访问儿子时下传。

    由于只能单个传,所以速度较慢。

    当然不止这三种操作,可以打标记且不改变相对大小的操作都可以。

    []

    例题

    Monkey King

    给定初始的 (n) 个大小为一的堆及其权值,再给定 (m) 次询问。
    每次询问 x y,表示将 (x)(y) 所在的堆的堆顶权值变为原来的一半后合并。
    对于每次操作,输出新堆的堆顶权值。
    (n,mle 100000)

    一开始的思路是,每次新建两个权值分别为两个堆顶权值一半的点,先将这两个点与这两个堆合并,再删除堆顶,最后合并两个堆。

    至于权值方面,由于我懒得写大根堆,于是就将权值的相反数插入堆中建小根堆。

    注意奇数的整除是下取整,所以对于奇数 (a),要将 a>>=1 写成 a=-((-a)>>1)

    计算下来若每次新建两个点,那么总共就是 (2 imes m) 个,加上原来的应该不超过 (300000) 个点。

    但不知道为什么总是 MLE,至今没找到原因(

    所以我换了写法,不再新建节点,而是先删除两个堆顶,再在每个堆中加入新点,只不过点的编号还是原来堆顶的。

    最后合并两个堆就做完了。

    代码和板子的差不多。

    namespace LIT{
      #define ls lson[x]
      #define rs rson[x]
      int fa[maxn],dis[maxn];
      int lson[maxn],rson[maxn];
      
      struct node{
        int pos,val;
        bool operator < (const node &b) const{
          return val^b.val?val<b.val:pos<b.pos;  
        } 
      }v[maxn];
      
      int findf(int x){
        return fa[x]==x?x:fa[x]=findf(fa[x]);
      }
      
      int merge(int x,int y){
        if(!x||!y) return x+y;
        if(v[y]<v[x]) swap(x,y);
        rs=merge(rs,y);
        if(dis[ls]<dis[rs])swap(ls,rs);
        dis[x]=dis[rs]+1;
        return x;
      }
    }
    
    using namespace LIT;
    
    signed main(){
      dis[0]=-1;
      while(scanf("%d",&n)!=EOF){
        for(int i=1;i<=n;i++){
          lson[i]=rson[i]=dis[i]=0;
          v[i].val=read()*-1;
          v[i].pos=fa[i]=i;
        }
        m=read();cnt=n;
        for(int i=1,x,y;i<=m;i++){
          x=findf(read());y=findf(read());
          if(x==y){printf("-1
    ");continue;}
          v[x].val=-((-v[x].val)>>1);
          int rt1=merge(lson[x],rson[x]);
          lson[x]=rson[x]=dis[x]=0;
          int now1=fa[rt1]=fa[x]=merge(rt1,x);
          v[y].val=-((-v[y].val)>>1);
          int rt2=merge(lson[y],rson[y]);
          lson[y]=rson[y]=dis[y]=0;
          int now2=fa[rt2]=fa[y]=merge(rt2,y);
          fa[now1]=fa[now2]=merge(now1,now2);
          printf("%d
    ",-v[fa[now1]].val);
        }
      }
      return 0;
    }
    
  • 相关阅读:
    Balanced Binary Tree
    Convert Sorted List to Binary Search Tree
    Convert Sorted Array to Binary Search Tree
    Binary Tree Zigzag Level Order Traversal
    Validate Binary Search Tree
    Binary Tree Level Order Traversal II
    Binary Tree Level Order Traversal
    Maximum Depth of Binary Tree
    如何把U盘的两个盘或者多个盘合成一个
    bugku 想蹭网先解开密码
  • 原文地址:https://www.cnblogs.com/KnightL/p/14932509.html
Copyright © 2020-2023  润新知