禁止套娃: 線段樹套宗法樹
阿新 • • 發佈:2021-06-17
樹套樹
這是一種思想, 不是什麼特定的資料結構, 不過實現起來一般都是從外層的樹形資料結構的每個節點上, 掛一個內層樹形資料結構的根的指標, 這樣, 本來是要查詢或修改外層節點的資訊的行為, 變成了在外層某節點對應的內層資料結構上查詢或修改某種資訊.
容易發現, 這種思想非常佔空間, 所以一般這種資料結構尤其是內層, 必須動態開點或直接實現可持久化.
模板
直接來看一道題來感受這種思想:
維護一個序列, 支援:
-
區間查詢第 \(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\)
所以對於 \(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;
}