1. 程式人生 > 其它 >禁止套娃: 線段樹套宗法樹

禁止套娃: 線段樹套宗法樹

樹套樹

這是一種思想, 不是什麼特定的資料結構, 不過實現起來一般都是從外層的樹形資料結構的每個節點上, 掛一個內層樹形資料結構的根的指標, 這樣, 本來是要查詢或修改外層節點的資訊的行為, 變成了在外層某節點對應的內層資料結構上查詢或修改某種資訊.

容易發現, 這種思想非常佔空間, 所以一般這種資料結構尤其是內層, 必須動態開點或直接實現可持久化.

模板

直接來看一道題來感受這種思想:

維護一個序列, 支援:

  • 區間查詢第 \(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\n", Ans);
    }
  }
  return Wild_Donkey;
}