「學習筆記」平衡樹基礎:Splay 和 Treap

2023-03-19 12:01:08

「學習筆記」平衡樹基礎:Splay 和 Treap

點選檢視目錄

知識點

平衡樹概述

二元搜尋樹(BST)的簡單定義:

  • 根節點的左子樹權值 \(<\) 根節點權值 \(<\) 根節點的右子樹權值;
  • 左子樹和右子樹均為二元搜尋樹。

這樣的資料結構可以維護一個集合的以下操作:

  • 查詢最小/最大值;
  • 插入一個元素;
  • 刪除一個元素;
  • 求元素的排名;
  • 查詢排名為 \(k\) 的元素。

該資料結構最優情況下單次查詢僅需 \(\Theta(\log_2{n})\) 的時間複雜度,但通過構造輸入可以使二元搜尋樹退化為鏈,達到 \(\Theta(n)\) 的時間複雜度。

所以我們要讓這棵二元搜尋樹儘量平衡(深度接近 \(\log_2{n}\))。

於是就誕生了平衡樹。

Splay

旋轉操作

可以發現左旋/右旋後樹的形態發生變化,但仍然滿足二元搜尋樹的性質。

Splay 操作

Splay 的核心操作,即把一個點提到根

分三種情況:

  1. 爹就是根:直接轉(\(\text{zig}\)
  2. 三點共線:先轉爹,再轉自己(\(\text{zig-zig}\)
  3. 三點不共線:直接轉兩下自己(\(\text{zig-zag}\)):

為什麼要這麼轉呢?因為直接單旋上去無法保證複雜度,隨隨便便就能卡掉,而雙旋的時間複雜度可以用勢能分析法進行分析

插入 \(x\)

根據 BST 的性質找到一個地方插入新點,然後把它旋上去。

查詢排名為 \(k\) 的數

假設當前到了點 \(p\)

  • \(k\) 小於 \(p\) 的左子樹大小時往左走
  • 否則令 \(k\) 減去 \(p\) 的左子樹大小,然後:
    • 如果當前節點副本數大於等於 \(k\),則當前節點的權值就是答案,然後把這個點旋上去。
    • 否則令 \(k\) 減去 \(p\) 的副本數。

查詢 \(x\) 的排名

找到 \(x\) 提到根,左子樹的大小加 \(1\) 就是答案。

查詢 \(x\) 的前驅

\(x\) 提到根後,在左子樹裡一路往右走。

查詢 \(x\) 的後繼

\(x\) 提到根後,在右子樹裡一路往左走。

刪除 \(x\)

最麻煩的一個。

首先我們把 \(x\) 提到根

  • 此時如果 \(x\) 副本數大於 \(1\) 則直接讓副本數減一。
  • 否則看兒子數量:
    • 沒兒子了:直接刪。
    • 一個兒子:讓兒子當根。
    • 兩個兒子:首先把 \(x\) 的前驅提上來,因為剛才 \(x\) 是根並且最後一次旋轉的時候不可能三點共線(不然就不是左子樹的最大值了),所以此時根的右兒子是 \(x\),且此時 \(x\) 無左兒子,所以直接把 \(x\) 的右兒子接到根的右兒子上就可以了。

程式碼

點選檢視程式碼
const ll N = 2e5 + 10, INF = 1ll << 40;
namespace Splay {
	class TreePoint {
	public:
		ll val, cnt, sz;
		ll fa, son[2];
		inline void New (ll num) { val = num, cnt = sz = 1, fa = son[0] = son[1] = 0; return; }
		inline void Clear () { val = cnt = sz = fa = son[0] = son[1] = 0; return; }
	};

	class splay {
	public:
		TreePoint tr[N];
		ll tot = 0, rt = 0;

#define va(p) tr[p].val
#define cn(p) tr[p].cnt
#define sz(p) tr[p].sz
#define fa(p) tr[p].fa
#define so(p, lr) tr[p].son[lr]

		inline void PushUp (ll p) { sz (p) = sz (so (p, 0)) + sz (so (p, 1)) + cn (p); return; } // Update the size.
		inline bool Get (ll p) { return p == so (fa (p), 1); } // Left son(0) or right son(1)?
		inline ll NewPoint (ll num) { tr[++tot].New (num); return tot; } // Build a new Point.

		inline void Rotate (ll p) {
			ll q = fa (p), r = fa (q), sd = Get (p);
			so (q, sd) = so (p, sd ^ 1);
			if (so (p, sd ^ 1)) fa (so (p, sd ^ 1)) = q;
			so (p, sd ^ 1) = q, fa (q) = p, fa (p) = r;
			if (r) so (r, q == so (r, 1)) = p;
			PushUp (q), PushUp (p);
			return;
		} // Zig or zag.
		inline void Splay (ll p) {
			for (ll q = fa (p); q = fa (p), q; Rotate (p))
				if (fa (q)) Rotate (Get (p) == Get (q) ? q : p);
			rt = p;
			return;
		} // Splay's core operations!

		inline void Out () {
			_for (i, 1, tot) {
				printf ("%lld : val=%lld cnt=%lld sz=%lld fa=%lld  %lld %lld\n", i, va (i), cn (i), sz (i), fa (i), so (i, 0), so (i, 1));
			}
			puts ("");
			return;
		} // For debug.

		inline void Insert (ll x) {
			if (!rt) rt = NewPoint (x), PushUp (rt);
			else {
				ll p = rt;
				while (1) {
					ll sd = (bool)(va (p) < x);
					if (va (p) == x) {
						++cn (p);
						PushUp (p), PushUp (fa (p));
						break;
					}
					else if (so (p, sd)) p = so (p, sd);
					else {
						so (p, sd) = NewPoint (x);
						fa (so (p, sd)) = p, p = so (p, sd);
						PushUp (p), PushUp (fa (p));
						break;
					}
				}
				Splay (p);
			}
			return;
		} // Insert a number x.
		inline ll GetRank (ll x) {
			ll p = rt, cnt = 1;
			while (p) {
				if (va (p) <= x) {
					cnt += sz (so (p, 0));
					if (va (p) == x) break;
					cnt += cn (p);
					p = so (p, 1);
				}
				else p = so (p, 0);
			}
			Splay (p);
			return cnt;
		} // Get x's rank.
		inline ll GetKth (ll x) {
			ll p = rt;
			while (p) {
				if (sz (so (p, 0)) < x) {
					x -= sz (so (p, 0));
					if (cn (p) >= x) break;
					x -= cn (p);
					p = so (p, 1);
				}
				else p = so (p, 0);
			}
			Splay (p);
			return va (p);
		} // Get k-th number.
		inline ll RealPre () {
			ll p = so (rt, 0);
			if (p) {
				while (so (p, 1)) p = so (p, 1);
				Splay (p);
			}
			return p;
		}
		inline void Delete (ll x) {
			GetRank (x);
			ll sd = (bool)(so (rt, 1));
			if (cn (rt) > 1) --cn (rt), PushUp (rt);
			else if (!so (rt, 0) && !so (rt, 1)) tr[rt].Clear (), rt = 0;
			else if (so (rt, 0) && so (rt, 1)) {
				ll p = rt, q = RealPre ();
				fa (so (p, 1)) = q;
				so (q, 1) = so (p, 1);
				tr[p].Clear ();
				PushUp (rt);
			}
			else {
				ll q = so (rt, sd);
				tr[rt].Clear ();
				fa (rt = q) = 0;
			}
			return;
		} // Delete a number x.
		inline ll Pre (ll x) {
			Insert (x);
			ll ans = va (RealPre ());
			Delete (x);
			return ans;
		} // Get x's pre.
		inline ll Next (ll x) {
			Insert (x);
			ll p = so (rt, 1), ans = 0;
			if (p) {
				while (so (p, 0)) p = so (p, 0);
				Splay (p);
			}
			ans = va (rt);
			Delete (x);
			return ans;
		} // Get x's next.

#undef va
#undef cn
#undef sz
#undef fa
#undef so
	};
}

替罪羊樹

很暴力的一個東西。

定義一個平衡因子 \(\alpha\)(最好選 \(0.7\sim0.8\)),插入/刪除一個節點的時候檢查是否存在一個節點的子樹的大小乘上 \(\alpha\) 小於左/右兒子樹的大小,如果有則把這棵子樹直接拍平重構。

其他操作和普通 BST 一樣。

點選檢視程式碼
namespace ScapeGoatTree {
	const ldb alpha = 0.75;
	const ll N = 1e5 + 10;
	class TreePoint {
	public:
		ll val, cnt, sz, cp, del;
		ll fa, son[2];
	};
	class SGT {
	private:
		ll tot = 0, rt = 0, dp[N], cd = 0;
		TreePoint tr[N];
		vec tmp, c;

#define va(p) tr[p].val
#define cn(p) tr[p].cnt
#define sz(p) tr[p].sz
#define de(p) tr[p].del
#define cp(p) tr[p].cp
#define fa(p) tr[p].fa
#define so(p, lr) tr[p].son[lr]

		inline ll NewP (ll num) {
			ll p = cd ? dp[cd--] : ++tot;
			va (p) = num;
			cn (p) = sz (p) = 1;
			cp (p) = fa (p) = so (p, 0) = so (p, 1) = de (p) = 0;
			return p;
		}
		inline void DelP (ll p) {
			va (p) = cn (p) = sz (p) = cp (p) = fa (p) = so (p, 0) = so (p, 1) = de (p) = 0;
			dp[++cd] = p;
			return;
		}
		inline void PushUp (ll p) {
			sz (p) = (de (p) ? 0 : cn (p)) + sz (so (p, 0)) + sz (so (p, 1));
			cp (p) = 1 + cp (so (p, 0)) + cp (so (p, 1));
			return;
		}

	public:
		inline void Clap (ll p) {
			if (so (p, 0)) Clap (so (p, 0));
			if (!de (p)) tmp.push_back (va (p)), c.push_back (cn (p));
			if (so (p, 1)) Clap (so (p, 1));
			DelP (p);
			return;
		}
		inline ll PullUp (ll l, ll r, ll fat) {
			if (l > r) return 0;
			bdmd;
			ll p = NewP (tmp[mid]);
			cn (p) = c[mid], fa (p) = fat;
			if (l < mid) so (p, 0) = PullUp (l, mid - 1, p);
			if (mid < r) so (p, 1) = PullUp (mid + 1, r, p);
			PushUp (p);
			return p;
		}
		inline void ReBuild (ll p) {
			ll q = fa (p);
			tmp.clear (), c.clear ();
			Clap (p);
			if (!q) rt = PullUp (0, tmp.size () - 1, q);
			else so (q, p == so (q, 1)) = PullUp (0, tmp.size () - 1, q);
			return;
		}

		inline bool Bad (ll p) {
			return cp (so (p, 0)) > cp (p) * alpha || cp (so (p, 1)) > cp (p) * alpha;
		}
		inline void Check (ll p) {
			ll q = 0;
			while (p) {
				if (Bad (p)) q = p;
				rt = p;
				PushUp (p);
				p = fa (p);
			}
			if (q) ReBuild (q);
			return;
		}

		inline void Insert (ll num) {
			ll p = rt;
			if (!rt) {
				rt = NewP (num);
				return;
			}
			while (1) {
				if (va (p) == num) {
					++cn (p);
					if (de (p)) de (p) = 0;
					Check (p);
					return;
				}
				ll sd = num > va (p);
				if (so (p, sd)) p = so (p, sd);
				else {
					so (p, sd) = NewP (num);
					fa (so (p, sd)) = p;
					Check (p);
					return;
				}
			}
			return;
		}
		inline void Delete (ll num) {
			ll p = rt;
			while (p) {
				if (va (p) == num) {
					--cn (p);
					if (cn (p) < 1) {
						cn (p) = 0;
						de (p) = 1;
					}
					Check (p);
					return;
				}
				ll sd = num > va (p);
				p = so (p, sd);
			}
			return;
		}
		inline ll GetRank (ll num) {
			ll p = rt, rk = 1;
			while (p) {
				if (va (p) > num) {
					p = so (p, 0);
					continue;
				}
				rk += sz (so (p, 0));
				if (num == va (p)) break;
				rk += cn (p);
				p = so (p, 1);
			}
			return rk;
		}
		inline ll GetKth (ll k) {
			ll p = rt;
			while (p) {
				if (sz (so (p, 0)) >= k) {
					p = so (p, 0);
					continue;
				}
				k -= sz (so (p, 0));
				if (k <= cn (p)) break;
				k -= cn (p);
				p = so (p, 1);
			}
			return va (p);
		}
		inline ll Pre (ll num) { return GetKth (GetRank (num) - 1); }
		inline ll Nxt (ll num) { return GetKth (GetRank (num + 1)); }

#undef va
#undef cn
#undef sz
#undef de
#undef cp
#undef fa
#undef so
	};
}

Treap

每個節點還要存一個隨機權值,使得整棵樹不僅對於原權值來說是一棵 BST,對於隨機權值來說還是一個堆,在隨機狀態下一棵 Treap 是比較 \(\log_2n\) 層的。當然不排除你臉太黑導致 Treap 退化成鏈的極小可能。

那麼插入的時候,如果新節點不滿足堆的性質了,需要往上旋轉。刪除的時候直接旋到葉子結點刪掉,或者只剩一個兒子的時候直接讓兒子代替自己。

其他操作和普通 BST 一樣。

點選檢視程式碼
namespace TREAP {
	class TreePoint {
	public:
		ll val, rk, sz, cnt;
		ll son[2];
		inline void NewP (ll x) { val = x, rk = rand (), cnt = sz = 1, son[0] = son[1] = 0;return; }
	};
	class Treap {
	public:
		TreePoint tr[N];
		ll tot = 0, rt = 0;

#define va(p) tr[p].val
#define rk(p) tr[p].rk
#define sz(p) tr[p].sz
#define cn(p) tr[p].cnt
#define so(p, lr) tr[p].son[lr]

		inline void PushUp (ll p) { sz (p) = cn (p) + sz (so (p, 0)) + sz (so (p, 1)); return; }
		inline ll NewP (ll num) { tr[++tot].NewP (num); return tot; }
		inline void DelP (ll p) { va (p) = rk (p) = sz (p) = cn (p) = so (p, 0) = so (p, 1) = 0;return; }

		inline void Rotate (ll& p, ll sd) {
			ll q = so (p, sd ^ 1);
			so (p, sd ^ 1) = so (q, sd);
			so (q, sd) = p, p = q;
			PushUp (so (q, sd)), PushUp (q);
			return;
		}

		void Insert (ll& p, ll x) {
			if (!p) {
				p = NewP (x);
				return;
			}
			if (va (p) == x) ++cn (p);
			else {
				bool sd = (va (p) < x);
				Insert (so (p, sd), x);
				if (rk (so (p, sd)) > rk (p)) Rotate (p, sd ^ 1);
			}
			PushUp (p);
			return;
		}
		void Delete (ll& p, ll x) {
			if (!p) return;
			if (va (p) == x) {
				if (cn (p) == 1) {
					if (!so (p, 0) && !so (p, 1)) DelP (p), p = 0;
					else if (!so (p, 0) || !so (p, 1)) p = so (p, 0) + so (p, 1);
					else {
						ll sd = (rk (so (p, 1)) < rk (so (p, 0)));
						Rotate (p, sd), Delete (so (p, sd), x);
					}
				}
				else --cn (p);
			}
			else Delete (so (p, (x > va (p))), x);
			PushUp (p);
			return;
		}
		ll GetRank (ll p, ll x) {
			if (!p) return 1;
			if (va (p) == x) return 1 + sz (so (p, 0));
			if (va (p) < x) return GetRank (so (p, 1), x) + sz (so (p, 0)) + cn (p);
			return GetRank (so (p, 0), x);
		}
		ll GetKth (ll p, ll x) {
			if (!p) return 0;
			if (x <= sz (so (p, 0))) return GetKth (so (p, 0), x);
			x -= sz (so (p, 0));
			if (x <= cn (p)) return va (p);
			x -= cn (p);
			return GetKth (so (p, 1), x);
		}
		ll Pre (ll x) { return GetKth (rt, GetRank (rt, x) - 1); }
		ll Next (ll x) { return GetKth (rt, GetRank (rt, x + 1)); }

#undef va
#undef rk
#undef sz
#undef cn
#undef so
	};
}

FHQ_Treap

FHQ_Treap 依舊滿足 Treap 的性質,但是操作方式很神奇!

FHQ_Treap 也被稱為無旋 Treap,因為它的所有操作都沒有噁心的旋轉,只有分裂和合並兩個基礎操作!

分裂有兩種方法:按權值裂和按排名裂。一般來說,只當平衡樹的時候通常按權值裂,維護序列的時候按排名裂。具體怎麼裂見程式碼和例題。

合併的時候要保證第一棵樹的所有權值都比第二棵樹小,注意合的過程中要保證 Treap 的性質。

為了方便寫我沒有寫副本數。

然後就是六個操作了:

  • 插入

    \(x\) 裂成 \(a,b\) 兩棵樹,然後按 \(a,x,b\) 的順序合起來

  • 刪除

    這絕對是刪除操作最簡單的平衡樹了!先按 \(x\) 裂成 \(a,b\) 兩棵樹,再把 \(a\)\(x-1\) 裂成 \(a,c\) 兩棵樹,此時

  • 查詢排名為 \(k\) 的數

    和普通 BST 一樣。

  • 查詢 \(x\) 的排名

    \(x-1\) 裂成 \(a,b\) 兩棵樹,\(a\) 的大小加一就是答案

  • 查詢 \(x\) 的前驅

    \(x-1\) 裂成 \(a,b\) 兩棵樹,\(a\) 裡的最大值

  • 查詢 \(x\) 的後繼

    \(x-1\) 裂成 \(a,b\) 兩棵樹,\(b\) 裡的最小值

點選檢視程式碼
namespace FHQ_TREAP {
	class TreeNode {
	public:
		ll val, rk, sz, son[2];
		inline void Add (ll num) { val = num, rk = rand (), sz = 1, son[0] = son[1] = 0; return; }
	};
	class FHQTreap {
	private:
		TreeNode tr[N];
		ll tot = 0, rt = 0, a, b, c;

#define va(p) tr[p].val
#define rk(p) tr[p].rk
#define sz(p) tr[p].sz
#define so(p, lr) tr[p].son[lr]

		inline ll NewP (ll num) { tr[++tot].Add (num); return tot; }
		inline void PushUp (ll p) { sz (p) = 1 + sz (so (p, 0)) + sz (so (p, 1)); return; }

		void Split (ll p, ll x, ll& l, ll& r) {
			if (!p) l = r = 0;
			else {
				if (va (p) <= x) l = p, Split (so (p, 1), x, so (p, 1), r);
				else r = p, Split (so (p, 0), x, l, so (p, 0));
				PushUp (p);
			}
			return;
		}
		inline ll Merge (ll l, ll r) {
			if (!l || !r) return l + r;
			if (rk (l) < rk (r)) {
				so (l, 1) = Merge (so (l, 1), r);
				PushUp (l);
				return l;
			}
			else {
				so (r, 0) = Merge (l, so (r, 0));
				PushUp (r);
				return r;
			}
		}

	public:
		inline void Insert (ll x) {
			Split (rt, x, a, b);
			rt = Merge (Merge (a, NewP (x)), b);
			return;
		}
		inline void Delete (ll x) {
			Split (rt, x, a, b);
			Split (a, x - 1, a, c);
			rt = Merge (Merge (a, Merge (so (c, 0), so (c, 1))), b);
			return;
		}
		inline ll GetRank (ll x) {
			Split (rt, x - 1, a, b);
			ll ans = sz (a) + 1;
			rt = Merge (a, b);
			return ans;
		}
		inline ll GetKth (ll x) {
			ll p = rt;
			while (p) {
				if (x <= sz (so (p, 0))) p = so (p, 0);
				else {
					x -= sz (so (p, 0)) + 1;
					if (!x) break;
					p = so (p, 1);
				}
			}
			return va (p);
		}
		inline ll Pre (ll x) {
			Split (rt, x - 1, a, b);
			ll p = a;
			while (so (p, 1)) p = so (p, 1);
			rt = Merge (a, b);
			return va (p);
		}
		inline ll Next (ll x) {
			Split (rt, x, a, b);
			ll p = b;
			while (so (p, 0)) p = so (p, 0);
			rt = Merge (a, b);
			return va (p);
		}

#undef va
#undef rk
#undef sz
#undef so
	};
}

樹套樹

用於維護單點修改和區間第 \(k\) 大,排名,前驅和後繼的查詢。

首先建立一棵線段樹,每個節點單建一棵平衡樹維護這個區間(線段樹的每一層有 \(n\) 個節點,一共 \(\log_2n\) 層,因此只有 \(n\log_2n\) 個節點,不會 TLE/MLE)。

然後是如何維護五個操作:

  • 單點修改:所有包含這個點的平衡樹刪除原來這個點的數,再插入新數。
  • \(x\) 的區間排名:將在這個區間內的平衡樹的 \(x\) 加起來。
  • \(x\) 的區間第 \(k\) 大:使用二分,找到一個數的區間排名是 \(k\)
  • \(x\) 的區間前驅:這個區間內的所有平衡樹中 \(x\) 的前驅最大值。
  • \(x\) 的區間後繼:這個區間內的所有平衡樹中 \(x\) 的後繼最小值。
點選檢視程式碼
const ll N = 1e5 + 10, inf = 2147483647;

namespace FHQ_TREAP {
	class TreeNode {
	public:
		ll val, rk, sz, son[2];
		inline void Add (ll num) noexcept { val = num, rk = rand (), sz = 1, son[0] = son[1] = 0; return; }
	} tr[N << 4];
	ll tot = 0, len = 0, free[N << 4];

#define va(p) tr[p].val
#define rk(p) tr[p].rk
#define sz(p) tr[p].sz
#define so(p, lr) tr[p].son[lr]

	class FHQTreap {
	private:
		ll rt = 0, a, b, c;

		inline void PushUp (ll p) noexcept { sz (p) = 1 + sz (so (p, 0)) + sz (so (p, 1)); }
		inline ll NewP (ll num) noexcept {
			ll p = len ? free[len--] : ++tot;
			tr[p].Add (num);
			return p;
		}
		inline void DelP (ll p) noexcept {
			va (p) = rk (p) = sz (p) = so (p, 0) = so (p, 1) = 0;
			free[++len] = p;
			return;
		}

		inline void Split (ll p, ll x, ll& l, ll& r) noexcept {
			if (!p) l = r = 0;
			else {
				if (va (p) <= x) l = p, Split (so (p, 1), x, so (p, 1), r);
				else r = p, Split (so (p, 0), x, l, so (p, 0));
				PushUp (p);
			}
			return;
		}
		inline ll Merge (ll l, ll r) noexcept {
			if (!l || !r) return l + r;
			if (rk (l) > rk (r)) {
				so (l, 1) = Merge (so (l, 1), r);
				PushUp (l);
				return l;
			}
			else {
				so (r, 0) = Merge (l, so (r, 0));
				PushUp (r);
				return r;
			}
		}

	public:
		inline void Insert (ll x) noexcept {
			Split (rt, x, a, b);
			rt = Merge (Merge (a, NewP (x)), b);
			return;
		}
		inline void Delete (ll x) noexcept {
			Split (rt, x, a, b);
			Split (a, x - 1, a, c);
			rt = Merge (Merge (a, Merge (so (c, 0), so (c, 1))), b);
			DelP (c);
			return;
		}
		inline ll GetRank (ll x) noexcept {
			Split (rt, x - 1, a, b);
			ll ans = sz (a);
			rt = Merge (a, b);
			return ans;
		}
		inline ll Pre (ll x) noexcept {
			Split (rt, x - 1, a, b);
			ll p = a;
			while (so (p, 1)) p = so (p, 1);
			rt = Merge (a, b);
			return va (p);
		}
		inline ll Next (ll x) noexcept {
			Split (rt, x, a, b);
			ll p = b;
			while (so (p, 0)) p = so (p, 0);
			rt = Merge (a, b);
			return va (p);
		}
	};

#undef va
#undef rk
#undef sz
#undef so
}

namespace SEGMENT_TREE {
	class SegmentTree {
	private:
		FHQ_TREAP::FHQTreap tr[N << 2];

#define ls(p) p << 1, l, mid
#define rs(p) p << 1 | 1, mid + 1, r

	public:
		inline void Build (ll p, ll l, ll r, ll* a) noexcept {
			if (l != r) {
				ll mid = md;
				Build (ls (p), a);
				Build (rs (p), a);
			}
			tr[p].Insert (inf), tr[p].Insert (-inf);
			_for (i, l, r) tr[p].Insert (a[i]);
			return;
		}
		inline void Update (ll p, ll l, ll r, ll x, ll y, ll z) noexcept {
			if (r < x || x < l) return;
			if (l != r) {
				ll mid = md;
				Update (ls (p), x, y, z);
				Update (rs (p), x, y, z);
			}
			tr[p].Delete (z), tr[p].Insert (y);
		}
		inline ll QueryRank (ll p, ll l, ll r, ll le, ll ri, ll x) noexcept {
			if (ri < l || r < le) return 0;
			ll mid = md;
			if (le <= l && r <= ri) return tr[p].GetRank (x) - 1;
			else return QueryRank (ls (p), le, ri, x) + QueryRank (rs (p), le, ri, x);
		}
		inline ll QueryKth (ll le, ll ri, ll x, ll n, ll mx) noexcept {
			ll l = 0, r = mx, ans = 0;
			while (l <= r) {
				ll mid = md;
				if (QueryRank (1, 1, n, le, ri, mid) + 1 <= x) ans = mid, l = mid + 1;
				else r = mid - 1;
			}
			return ans;
		}
		inline ll QueryPre (ll p, ll l, ll r, ll le, ll ri, ll x) noexcept {
			if (ri < l || r < le) return -inf;
			ll mid = md;
			if (le <= l && r <= ri) return tr[p].Pre (x);
			else return std::max (QueryPre (ls (p), le, ri, x), QueryPre (rs (p), le, ri, x));
		}
		inline ll QueryNext (ll p, ll l, ll r, ll le, ll ri, ll x) noexcept {
			if (ri < l || r < le) return inf;
			ll mid = md;
			if (le <= l && r <= ri) return tr[p].Next (x);
			else return std::min (QueryNext (ls (p), le, ri, x), QueryNext (rs (p), le, ri, x));
		}
	};
}

平衡樹的區間操作

平衡樹不是隻能平衡樹,也可以進行區間操作。

用平衡樹中序遍歷順序不變的性質,維護序列中每個數的前後位置(即把按數值排序變為按下標排序)。

操作時,把需要操作/查詢的區間放在一顆子樹上再直接對子樹進行操作/查詢。

如果操作對子樹的所有點生效,應該給子樹打個標記,旋轉/分裂/合併的時候隨手下傳標記。

具體的

使用 Splay:通過對 \(l-1\)\(r+1\) 提根使得區間 \([l, r]\) 在一顆子樹上,然後對這棵子樹進行操作。

使用 FHQ-Treap:把區間 \([l, r]\) 裂出來,然後對這棵子樹進行操作,再合併回去。

平衡樹還支援插入/刪除一個點,所以維護序列的時候還可以在任意位置加入/刪除一個數。

另外,FHQ-Treap 裂開合併的神奇操作還支援對一個區間進行移動。

例題

P3391 文藝平衡樹

思路

對需要操作的區間打個翻轉標記,同時交換其左右兒子。

P4036 [JSOI2008]火星人

思路

維護一棵子樹的字串的雜湊值,然後就是裸的區間操作了。

P4309 [TJOI2013]最長上升子序列

思路

不難發現每次插入的數都會比之前插入的數都大,因此插完之後最長上升子序列的長度不會變化。

那麼每個節點維護一個子樹最長上升子序列的長度的最大值,插入時選一個前面的最長的最長上升子序列接上。

星系探索

思路

把這棵樹轉換成 dfs 序序列,然後對於三個操作:

  • \(u\) 到根的權值和:求 \(u\) 在 dfs 序序列上的字首和。
  • 改變 \(u\) 的父親:移動 \(u\) 子樹的 dfs 序序列代表的區間的位置。
  • \(u\) 子樹加一個定值:把 \(u\) 子樹的 dfs 序序列代表的區間裂出來,打個標記再合併回去。

程式碼

點選檢視程式碼
const ll N = 1e6+ 10;

namespace FHQ_TREAP {
	class TreeNode {
	public:
		ll val, pn, rk, sz, so[2], ta, sum, sp, fa;
		inline void Add (ll num, ll tmp) { sum = val = num * tmp, sp = pn = tmp, rk = rand (), sz = 1, so[0] = so[1] = 0; return; }
	};
	class FHQTreap {
	private:
		TreeNode tr[N];
		ll tot = 0, rt = 0, a, b, c;

#define va(p) tr[p].val
#define ta(p) tr[p].ta
#define rk(p) tr[p].rk
#define sz(p) tr[p].sz
#define pn(p) tr[p].pn
#define su(p) tr[p].sum
#define sp(p) tr[p].sp
#define fa(p) tr[p].fa
#define so(p, lr) tr[p].so[lr]

		inline ll NewP (ll num, ll pn) { tr[++tot].Add (num, pn); return tot; }
		inline void PushUp (ll p) {
			sz (p) = 1 + sz (so (p, 0)) + sz (so (p, 1));
			su (p) = va (p) + su (so (p, 0)) + su (so (p, 1));
			sp (p) = pn (p) + sp (so (p, 0)) + sp (so (p, 1));
			fa (so (p, 0)) = fa (so (p, 1)) = p;
			return;
		}
		inline void Tag (ll p, ll num) {
			ta (p) += num;
			va (p) += num * pn (p);
			su (p) += num * sp (p);
			return;
		}
		inline void PushDown (ll p) {
			if (!ta (p)) return;
			if (so (p, 0)) Tag (so (p, 0), ta (p));
			if (so (p, 1)) Tag (so (p, 1), ta (p));
			fa (so (p, 0)) = fa (so (p, 1)) = p;
			ta (p) = 0;
			return;
		}

		inline void Split (ll p, ll x, ll& l, ll& r) {
			if (!p) l = r = 0;
			else {
				PushDown (p);
				if (sz (so (p, 0)) < x) l = p, Split (so (p, 1), x - sz (so (p, 0)) - 1, so (p, 1), r);
				else r = p, Split (so (p, 0), x, l, so (p, 0));
				PushUp (p);
			}
			return;
		}
		inline ll Merge (ll l, ll r) {
			PushDown (l), PushDown (r);
			if (!l || !r) return l + r;
			if (rk (l) > rk (r)) {
				so (l, 1) = Merge (so (l, 1), r);
				PushUp (l);
				return l;
			}
			else {
				so (r, 0) = Merge (l, so (r, 0));
				PushUp (r);
				return r;
			}
		}
		inline ll GetRank (ll x) {
			ll cnt = sz (so (x, 0)) + 1;
			for (ll p = x; fa (p); p = fa (p))
				if (so (fa (p), 1) == p) cnt += sz (so (fa (p), 0)) + 1;
			return cnt;
		}

	public:
		inline void Insert (ll x, ll p) {
			rt = Merge (rt, NewP (x, p));
			return;
		}
		inline void Modify (ll l, ll r, ll x) {
			l = GetRank (l), r = GetRank (r);
			Split (rt, r, a, b);
			Split (a, l - 1, a, c);
			Tag (c, x);
			rt = Merge (Merge (a, c), b);
			return;
		}
		inline void Move (ll l, ll r, ll x) {
			l = GetRank (l), r = GetRank (r), x = GetRank (x);
			if (x > r) x -= r - l + 1;
			Split (rt, r, a, b);
			Split (a, l - 1, a, c);
			a = Merge (a, b);
			Split (a, x, a, b);
			rt = Merge (Merge (a, c), b);
			return;
		}
		inline ll Query (ll x) {
			x = GetRank (x);
			Split (rt, x, a, b);
			ll ans = su (a);
			rt = Merge (a, b);
			return ans;
		}

#undef va 
#undef ta
#undef rk
#undef sz
#undef pn
#undef su
#undef sp
#undef fa
#undef so
	};
}

namespace SOLVE {
	FHQ_TREAP::FHQTreap tr;
	ll n, m, fa[N], w[N], cnt;
	ll dfn[N], sec[N][2];
	std::vector<ll> tu[N];
	inline ll rnt () {
		ll x = 0, w = 1; char c = getchar ();
		while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
		while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
		return x * w;
	}
	inline char rch () {
		char c = getchar ();
		while (c < 'A' || 'Z' < c) c = getchar ();
		return c;
	}
	inline void Dfs (ll x) {
		dfn[sec[x][0] = ++cnt] = x;
		tr.Insert (w[x], 1);
		far (i, tu[x]) Dfs (i);
		sec[x][1] = ++cnt;
		tr.Insert (w[x], -1);
		return;
	}
	inline void In () {
		n = rnt ();
		_for (i, 2, n) {
			fa[i] = rnt ();
			tu[fa[i]].push_back (i);
		}
		_for (i, 1, n) w[i] = rnt ();
		m = rnt ();
		Dfs (1);
		return;
	}
	inline void Out () {
		while (m--) {
			char opt = rch ();
			if (opt == 'Q') {
				ll d = rnt ();
				printf ("%lld\n", tr.Query (sec[d][0]));
			}
			else if (opt == 'C') {
				ll x = rnt (), y = rnt ();
				tr.Move (sec[x][0], sec[x][1], sec[y][0]);
			}
			else {
				ll x = rnt (), y = rnt ();
				tr.Modify (sec[x][0], sec[x][1], y);
			}
		}
		return;
	}
}

\[\Huge\mathfrak{The End} \]