• 左偏树


    左偏树,是一种可以在 (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;
    }
    
  • 相关阅读:
    ubuntu下/etc/rc.local和/etc/init.d/rc.local的区别
    Shell 中的中括号用法总结
    基于C语言sprintf函数的深入理解
    linux下查看十六进制文件方法
    oracle load data infile
    linux bash中too many arguments问题的解决方法
    关于促进问题解决
    SYSTEM表空间过大问题
    01 Oracle分区索引
    oracle 11gR2 ASM添加和删除磁盘 转
  • 原文地址:https://www.cnblogs.com/KnightL/p/14932509.html
Copyright © 2020-2023  润新知