zoukankan      html  css  js  c++  java
  • 禁止套娃: 线段树套宗法树

    树套树

    这是一种思想, 不是什么特定的数据结构, 不过实现起来一般都是从外层的树形数据结构的每个节点上, 挂一个内层树形数据结构的根的指针, 这样, 本来是要查询或修改外层节点的信息的行为, 变成了在外层某节点对应的内层数据结构上查询或修改某种信息.

    容易发现, 这种思想非常占空间, 所以一般这种数据结构尤其是内层, 必须动态开点或直接实现可持久化.

    模板

    直接来看一道题来感受这种思想:

    维护一个序列, 支持:

    • 区间查询第 (k) 小的数

    • 区间查排名

    • 单点修改

    • 区间查前驱

    • 区间查后继

    官方做法是用线段树做外层数据结构, 每个节点挂一棵平衡树, 存储这个线段树节点表示的区间的数, 因为平衡树本来就是动态开点的, 所以空间是可以接受的, 加起来是 (O(nlogn)).

    区间查排名, 只需要 (O(logn)) 找出这个区间对应的 (O(logn)) 棵平衡树, 然后对每个平衡树 (O(logn)) 查询排名, 总共 (O(log^2n)).

    单点修改也很简单, (O(logn)) 找出包含这个单点的 (O(logn)) 棵平衡树, 每棵平衡树 (O(logn)) 删除, (O(logn)) 插入即可.

    区间查前驱后继也很简单, 对 (O(logn)) 棵平衡树分别进行 (1) 次复杂度为 (O(logn)) 次查询前驱/后继, 取最大/最小的那个, 总时间 (O(log^2n)).

    对于区间查询第 (k) 小的数, 因为是在 (logn) 棵树上查询, 所以不能像普通平衡树一样, 二分查找这个位置, (O(logn)) 查询. 只能二分答案, (O(logn)) 的二分答案, (O(logn)) 的线段树上查询, (O(logn)) 的平衡树上查询, 总复杂度 (O(log^3n)).

    所以对于 (m) 次操作的总复杂度应该是 (O(mlog^3n)).

    代码

    首先是平衡树部分, 这里采用了宗法树.

    struct SubNode {
      SubNode *LS, *RS;
      int Val, Ival;
      unsigned Size;
    } SN[2000005], *CntSN(SN);
    

    旋转

    void Rotate(SubNode *x) {
      x->Size = x->LS->Size + x->RS->Size;
      x->Ival = x->LS->Ival, x->Val = x->RS->Val;
      if(x->LS->Size * 3 < x->RS->Size) {
        register SubNode *Move(x->RS);
        x->RS = Move->RS;
        Move->RS = Move->LS;
        Move->LS = x->LS;
        x->LS = Move;
        Move->Ival = Move->LS->Ival, Move->Val = Move->RS->Val, Move->Size = Move->LS->Size + Move->RS->Size;
        return;
      }
      if(x->RS->Size * 3 < x->LS->Size) {
        register SubNode *Move(x->LS);
        x->LS = Move->LS;
        Move->LS = Move->RS;
        Move->RS = x->RS;
        x->RS = Move;
        Move->Ival = Move->LS->Ival, Move->Val = Move->RS->Val, Move->Size = Move->LS->Size + Move->RS->Size; 
      }
    }
    

    插入

    SubNode *Insert(SubNode *x) {
      if(x->Size == 1) {
        SubNode *Fa(++CntSN);
        if(x->Val < OpVal) {
          Fa->LS = x;
          Fa->RS = ++CntSN;
        } else {
          Fa->RS = x;
          Fa->LS = ++CntSN;
        }
        CntSN->Val = CntSN->Ival = OpVal, CntSN->Size = 1;
        Fa->Size = 2;
        Fa->Ival = Fa->LS->Ival, Fa->Val = Fa->RS->Val;
        return Fa;
      }
      if(x->LS->Val < OpVal) x->RS = Insert(x->RS);
      else x->LS = Insert(x->LS);
      Rotate(x);
      return x;
    }
    

    删除

    SubNode *Delete(SubNode *x) {
      if(x->LS->Val < OpTmp) {
        if(x->RS->Size == 1) {
          if(x->RS->Val == OpTmp) {
            return x->LS;
          } else {
            return x;
          }
        }
        x->RS = Delete(x->RS);
      } else {
        if(x->LS->Size == 1) {
          if(x->LS->Val == OpTmp) {
            return x->RS;
          } else {
            return x;
          }
        }
        x->LS = Delete(x->LS);
      }
      Rotate(x);
      return x; 
    }
    

    查排名

    void SubRank(SubNode *x) {
      if (x->Size == 1) {
        if (x->Val < OpVal) ++Ans;
        return;
      }
      if (x->LS->Val < OpVal)
        Ans += x->LS->Size, SubRank(x->RS);
      else
        SubRank(x->LS);
    }
    

    查前驱

    void SubPre(SubNode *x) {
      if (x->Size == 1) {
        if (x->Val < OpVal) Ans = max(Ans, x->Val);
        return;
      }
      if (x->RS->Ival >= OpVal) {
        SubPre(x->LS);
      } else {
        SubPre(x->RS);
      }
    }
    

    查后继

    void SubSuc(SubNode *x) {
      if (x->Size == 1) {
        if (x->Val > OpVal) Ans = min(Ans, x->Val);
        return;
      }
      if (x->LS->Val < OpVal) {
        SubSuc(x->RS);
      } else {
        SubSuc(x->LS);
      }
    }
    

    然后是线段树部分, 最普通的线段树即可.

    struct Node {
      Node *LS, *RS;
      SubNode *Root;
    } N[100005], *CntN(N);
    

    建树

    void Build(Node *x, unsigned L, unsigned R) {
      x->Root = ++CntSN;
      x->Root->Val = x->Root->Ival = a[L];
      x->Root->Size = 1;
      if (L == R) return;
      for (register unsigned i(L + 1); i <= R; ++i) {
        OpVal = a[i], x->Root = Insert(x->Root);
      }
      register unsigned Mid((L + R) >> 1);
      Build(x->LS = ++CntN, L, Mid);
      Build(x->RS = ++CntN, Mid + 1, R);
    }
    

    单点修改

    就是先插入一个新值, 然后将原值删除.

    void Change(Node *x, unsigned L, unsigned R) {
      x->Root = Insert(x->Root);
      x->Root = Delete(x->Root);
      if (L == R) {
        return;
      }
      register unsigned Mid((L + R) >> 1);
      if (Mid < OpL) {
        Change(x->RS, Mid + 1, R);
      } else {
        Change(x->LS, L, Mid);
      }
      return;
    }
    

    查排名

    void Rank(Node *x, unsigned L, unsigned R) {
      if (L >= OpL && R <= OpR) {
        SubRank(x->Root);
        return;
      }
      register unsigned Mid((L + R) >> 1);
      if (OpL <= Mid) {
        Rank(x->LS, L, Mid);
      }
      if (Mid < OpR) {
        Rank(x->RS, Mid + 1, R);
      }
      return;
    }
    

    查前驱

    void Pre(Node *x, unsigned L, unsigned R) {
      if (L >= OpL && R <= OpR) {
        return SubPre(x->Root);
      }
      register unsigned Mid((L + R) >> 1);
      if (OpL <= Mid) {
        Pre(x->LS, L, Mid);
      }
      if (Mid < OpR) {
        Pre(x->RS, Mid + 1, R);
      }
    }
    

    查后继

    void Suc(Node *x, unsigned L, unsigned R) {
      if (L >= OpL && R <= OpR) {
        return SubSuc(x->Root);
      }
      register unsigned Mid((L + R) >> 1);
      if (OpL <= Mid) {
        Suc(x->LS, L, Mid);
      }
      if (Mid < OpR) {
        Suc(x->RS, Mid + 1, R);
      }
    }
    

    二分答案

    void Find() {
      register int L(0), R(100000000), Mid;
      while (L < R) {
        Mid = ((L + R + 1) >> 1);
        Ans = 1, OpVal = Mid, Rank(N, 1, n);
        if (Ans > OpTmp)
          R = Mid - 1;
        else
          L = Mid;
      }
      Ans = L;
    }
    

    主函数

    unsigned a[50005], m, n, Cnt(0), OpL, OpR, A, B, C, D, t, Tmp(0);
    int Ans, OpVal, OpTmp;
    int main() {
      n = RD(), m = RD();
      for (register unsigned i(1); i <= n; ++i) a[i] = RD();
      Build(N, 1, n);
      for (register unsigned i(1); i <= m; ++i) {
        A = RD();
        switch (A) {
          case 1: {
            OpL = RD(), OpR = RD(), OpVal = RD();
            Ans = 1, Rank(N, 1, n);
            break;
          }
          case 2: {
            OpL = RD(), OpR = RD(), OpTmp = RD();
            Find();
            break;
          }
          case 3: {
            OpL = RD();
            OpTmp = a[OpL];
            a[OpL] = OpVal = RD();
            Change(N, 1, n);
            break;
          }
          case 4: {
            OpL = RD(), OpR = RD(), OpVal = RD();
            Ans = -2147483647, Pre(N, 1, n);
            break;
          }
          case 5: {
            OpL = RD(), OpR = RD(), OpVal = RD();
            Ans = 2147483647, Suc(N, 1, n);
            break;
          }
        }
        if (A != 3) {
          printf("%d
    ", Ans);
        }
      }
      return Wild_Donkey;
    }
    
  • 相关阅读:
    Dapper数据库字段和model属性映射
    SQLServer性能调优之缓存
    python&django 实现页面中关联查询小功能(基础篇)
    pythonのdjango select_related 和 prefetch_related()
    pythonのdjango 在控制台用log打印操作日志
    pythonのdjango Form简单应用。
    pythonのdjango 信号
    pythonのdjango 缓存
    pythonのdjango CSRF简单使用
    pythonのdjango Session体验
  • 原文地址:https://www.cnblogs.com/Wild-Donkey/p/14897458.html
Copyright © 2011-2022 走看看