Compare commits

...

2 Commits

Author SHA1 Message Date
e2hang
3c5d314e48 Advanced Data Structure 2026-02-08 16:39:45 +08:00
e2hang
ec53ee7cab Auto commit at 2026-01-26 10:49:53 2026-01-26 10:49:53 +08:00
89 changed files with 2193 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 建议使用 N 定义数组,比 vector 稍快且容易管理
const int N = 100005;
vector<pair<int, int>> adj[N];
bool vis[N]; // 分治标记:记录哪些点已经作为重心被“删掉”了
int treesize[N];
int root, min_maxpart, K;
long long ans = 0;
// 存储当前子树所有节点到重心的距离
vector<int> dists;
// 1. 找重心:注意这里不再需要 visited 数组,用 fa 限制方向即可
void get_root(int u, int fa, int currsize) {
treesize[u] = 1;
int maxpart = 0;
for (auto& edge : adj[u]) {
int v = edge.first;
if (v == fa || vis[v]) continue; // 关键:不能走已经“删掉”的点
get_root(v, u, currsize);
treesize[u] += treesize[v];
maxpart = max(maxpart, treesize[v]);
}
maxpart = max(maxpart, currsize - treesize[u]);
if (maxpart < min_maxpart) {
min_maxpart = maxpart;
root = u;
}
}
// 2. 收集距离:把所有节点到重心的距离存入 dists 数组
void get_dists(int u, int fa, int d) {
dists.push_back(d);
for (auto& edge : adj[u]) {
int v = edge.first;
int w = edge.second;
if (v == fa || vis[v]) continue;
get_dists(v, u, d + w);
}
}
// 3. 计算贡献:双指针法统计满足 dist1 + dist2 <= K 的对数
long long calc(int u, int init_dist) {
dists.clear();
get_dists(u, -1, init_dist);
sort(dists.begin(), dists.end());
long long count = 0;
int l = 0, r = dists.size() - 1;
while (l < r) {
if (dists[l] + dists[r] <= K) {
count += (r - l);
l++;
} else {
r--;
}
}
return count;
}
// 4. 点分治主函数
void divide(int u, int currsize) {
// 每次进入先找当前连通块的重心
min_maxpart = currsize + 7; // 初始化为一个大值
get_root(u, -1, currsize);
int r = root; // 锁定重心
vis[r] = true; // 【关键】逻辑切断重心
// 统计经过重心的路径
ans += calc(r, 0);
// 递归子树
for (auto& edge : adj[r]) {
int v = edge.first;
int w = edge.second;
if (vis[v]) continue;
// 【关键】去重:减去在同一个儿子子树内提前满足条件的路径
ans -= calc(v, w);
// 这里的 currsize 要更新为子树的实际大小
// 注意:由于已经跑过 get_roottreesize[v] 此时就是正确的
// 但如果 v 是 r 的“上方”节点size 应该是 currsize - treesize[r]
int next_size = (treesize[v] < treesize[r]) ? treesize[v] : (currsize - treesize[r]);
divide(v, next_size);
}
}
int main() {
int n;
if (!(cin >> n >> K)) return 0;
for (int i = 0; i < n - 1; i++) {
int u, v, w;
cin >> u >> v >> w;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
divide(1, n); // 从节点1开始分治总大小为 n
cout << ans << endl;
return 0;
}

View File

@@ -0,0 +1,153 @@
# P1824 [USACO05FEB] 进击的奶牛 Aggressive Cows G
## 题目描述
农夫约翰建造了一座有 $n$ 间牛舍的小屋,牛舍排在一条直线上,第 $i$ 间牛舍在 $x_i$ 的位置,但是约翰的 $m$ 头牛对小屋很不满意,因此经常互相攻击。约翰为了防止牛之间互相伤害,因此决定把每头牛都放在离其它牛尽可能远的牛舍。也就是要最大化最近的两头牛之间的距离。
牛们并不喜欢这种布局,而且几头牛放在一个隔间里,它们就要发生争斗。为了不让牛互相伤害。约翰决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是多少呢?
## 输入格式
第一行用空格分隔的两个整数 $n$ 和 $m$
下面 $n$ 行为 $n$ 个用空格隔开的整数,表示位置 $x_i$。
## 输出格式
一行一个整数,表示最大的最小距离值。
## 输入输出样例 #1
### 输入 #1
```
5 3
1
2
8
4
9
```
### 输出 #1
```
3
```
## 说明/提示
【样例解析】把牛放在 $1$$4$$8$ 这三个位置,距离是 $3$。容易证明最小距离已经最大。
【数据范围】对于 $100\%$ 的数据,$2 \le n \le 10^5$$0 \le x_i \le 10^9$$2 \le m \le n$。不保证 $x$ 数组单调递增。
哈哈,这种反应太正常了!初次接触这种题,谁都会觉得这种“反着来”的思路简直是脑洞大开。
这种思维方式在算法竞赛里有一个专门的套路,叫 **“二分答案”**。其实它不是靠灵光一现,而是有迹可循的“职业习惯”。
我们可以把这种思维过程拆解为三个“直觉点”,以后你看到类似的题,也能秒懂。
---
### 1. 关键词暗示:看到“最大化最小值”
当你看到题目要求:
* **最大化** 某个 **最小值**
* 或者 **最小化** 某个 **最大值**
这在算法界几乎就是**明示**:请用二分搜索!
**为什么?** 因为直接计算“最优布局”非常困难,要考虑无数种组合。但如果我问你:“间距为 行不行?”你只需要从左往右摆摆看(贪心),一眼就能看出行不行。把“寻找题”变成“判断题”,难度瞬间降维。
### 2. 利用“单调性”:答案像个开关
这是最关键的直觉。你可以这样想:
* 如果间距是1 米,肯定能放下 头牛(太简单了)。
* 如果间距是1000000 米,肯定放不下(太远了)。
* 在这中间,一定存在一个**临界点**。在临界点左边,全是“行”;在临界点右边,全是“不行”。
既然有这种**单调性**(一半行,一半不行),我们就不需要一个个试,直接用**二分查找**去撞那个临界点。
### 3. 程序员的“偷懒”思维
正向思考(直接算位置)是“创造者思维”,很难;
反向思考(猜一个答案去验证)是“检查者思维”,很简单。
**计算机最擅长做简单的重复劳动。** 既然验证一次只需要 ,二分查找只需要 次,总共也就跑 次验证。与其想破脑袋去算最优解,不如直接“暴力”地猜几十次。
---
### 总结一下这个“坑”是怎么跳进去的:
1. **第一步:** 看到“最大化最小间距”,心里默念:“二分答案预警”。
2. **第二步:** 确定答案范围(比如 到 )。
3. **第三步:** 写一个 `check` 函数。这一步通常用**贪心**。
* *贪心逻辑:* 既然要间距大,我就把第一头牛放在最左边,后面每一头牛都紧贴着“当前位置 + 间距”能放下的第一个点放。
4. **第四步:** 套二分模板。
> **这种感觉就像是:** 你去买衣服,不知道自己穿多大码。你不是拿尺子把自己全身量一遍再算缩水率(正向计算),而是直接拿 L 码试一下。大了?换 M。还大换 S。这就是二分答案。
**你要不要试着写一下这个 `check` 函数的伪代码?我可以帮你看看逻辑对不对。**
``` cpp
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
//int ax[] = {1, 2, 4, 8, 9};
//按照分布,检查是否能够排列即可
/*
1、读入arr, mid
2、按照分布排列找到离mid格最近的槽位插入
*/
int check(const vector<int>& arr, int mid){
if(arr.empty()) return 0;
int cnt = 1;
int n = arr.size();
int dist = 0;
for(int i = 1; i < n; ++i){
dist += arr[i] - arr[i - 1];
if(dist >= mid){
cnt++;
dist = 0;
}
}
return cnt;
}
int main(){
int n = 5, c = 3;
cin >> n >> c;
vector<int> arr(n);
for(int i = 0; i < n; i++){
cin >> arr[i];
//arr[i] = ax[i];
}
sort(arr.begin(), arr.end());
int smin = 0, smax = arr.back() - arr.front();
//range [smin, smax], do mid to this range
int l = smin, r = smax;
int ans = 0;
while(l <= r){
int mid = (l+r)/2;
int cc = check(arr, mid);
if (cc >= c) {
ans = mid;
l = mid + 1;
} else r = mid - 1;
}
cout << ans << endl;
return 0;
}
```

View File

@@ -0,0 +1,79 @@
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
/*
* L Stands for Left Dividing Point, So as R
* pos stands for: for each element, which Chunk the element is in
* sum stands for the main purpose, what do we want the chunk to calculate
*/
vector<int> L, R, pos, sum, tag;
void build(int n, const vector<int>& arr){
int B = sqrt(n);
int num = n / B; if(n % B) num++;
L.resize(num); R.resize(num); sum.resize(num); tag.resize(num);
for(int i = 0; i <= num; ++i) {
L[i] = (i-1)*B + 1;
R[i] = min(i*B, n); // Remind of The Last dec
for(int j = L[i]; j <= R[i]; ++j){
pos[j] = i;
sum[i] += arr[j];
}
}
}
void update(vector<int>& arr, int l, int r, int v){
int lchk = pos[l], rchk = pos[r];
//A. Only in one chunk
if (lchk == rchk) {
for(int i = l; i <= r; i++){
arr[i] += v;
sum[lchk] += v;
}
} else {
//B. Across Chunks
//left & right: violent update
for(int i = l; i <= R[lchk]; ++i){
arr[i] += v;
sum[lchk] += v;
}
for(int i = L[rchk]; i <= r; ++i){
arr[i] += v;
sum[rchk] += v;
}
//middle: use tag
for(int i = lchk+1; i <= rchk-1; ++i){
tag[i] += v;
}
}
}
long long query(const vector<int>& arr, int l, int r){
int lchk = pos[l], rchk = pos[r];
//A. Only in one chunk
long long ans = 0;
if (lchk == rchk) {
for(int i = l; i <= r; i++){
ans += arr[i] + tag[lchk];
}
} else {
//B. Across Chunks
//left & right: violent update
for(int i = l; i <= R[lchk]; ++i){
ans += arr[i] + tag[lchk];
}
for(int i = L[rchk]; i <= r; ++i){
ans += arr[i] + tag[rchk];
}
//middle: use tag
for(int i = lchk+1; i <= rchk-1; ++i){
ans += sum[i] + tag[i] * (R[i]-L[i] + 1);
}
}
return ans;
}
int main(){
return 0;
}

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

View File

@@ -0,0 +1,48 @@
#include <iostream>
#include <vector>
using namespace std;
inline int lowbit(int x){
return (x & (-x));
}
class BIT{
public:
vector<long long> tree; // 用 long long 防溢出
int n;
BIT(int size) : n(size) {
tree.resize(n+1, 0); // *BIT 空间通常是 n+1*
}
BIT(vector<int> arr) {
n = arr.size();
tree.resize(n+1, 0);
for(int i = 0; i < n; ++i){
int x = i + 1;
while(x <= n){
tree[x] += arr[i];
x += lowbit(x);
}
}
}
//x: pos; d: alter a[x] to a[x] + d;
void update(int x, int d) {
if (x <= 0) return;
while (x <= n) {
tree[x] += d;
x += lowbit(x);
}
}
//return sum: a[0] + a[1] + ... + a[x]
long long sum(int x) {
long long ans = 0;
while (x > 0) {
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
};
int main(){
}

View File

@@ -0,0 +1,91 @@
#include <bits/stdc++.h>
using namespace std;
/* ========= 2D KD-Tree ========= */
struct Point {
double x, y;
};
struct Node {
Point p;
Node *left, *right;
Node(Point _p) : p(_p), left(nullptr), right(nullptr) {}
};
double dist2(const Point& a, const Point& b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return dx * dx + dy * dy;
}
Node* build(vector<Point>& pts, int l, int r, int depth) {
if (l >= r) return nullptr;
int dim = depth % 2;
int mid = (l + r) / 2;
nth_element(pts.begin() + l, pts.begin() + mid, pts.begin() + r,
[&](const Point& a, const Point& b) {
return dim == 0 ? a.x < b.x : a.y < b.y;
});
Node* node = new Node(pts[mid]);
node->left = build(pts, l, mid, depth + 1);
node->right = build(pts, mid + 1, r, depth + 1);
return node;
}
void nearest(Node* node, const Point& target, int depth,
Point& best, double& bestDist) {
if (!node) return;
double d = dist2(node->p, target);
if (d < bestDist) {
bestDist = d;
best = node->p;
}
int dim = depth % 2;
Node *nearChild, *farChild;
if ((dim == 0 && target.x < node->p.x) ||
(dim == 1 && target.y < node->p.y)) {
nearChild = node->left;
farChild = node->right;
} else {
nearChild = node->right;
farChild = node->left;
}
nearest(nearChild, target, depth + 1, best, bestDist);
double diff = (dim == 0)
? target.x - node->p.x
: target.y - node->p.y;
if (diff * diff < bestDist) {
nearest(farChild, target, depth + 1, best, bestDist);
}
}
int main() {
vector<Point> points = {
{2,3}, {5,4}, {9,6},
{4,7}, {8,1}, {7,2}
};
Node* root = build(points, 0, points.size(), 0);
Point target{9, 2};
Point best;
double bestDist = 1e18;
nearest(root, target, 0, best, bestDist);
cout << "Nearest point: (" << best.x << ", " << best.y << ")\n";
cout << "Squared distance: " << bestDist << "\n";
return 0;
}

View File

@@ -0,0 +1,99 @@
#include <bits/stdc++.h>
using namespace std;
/* ========= 3D KD-Tree ========= */
struct Point {
double x, y, z;
};
struct Node {
Point p;
Node *left, *right;
Node(Point _p) : p(_p), left(nullptr), right(nullptr) {}
};
double dist2(const Point& a, const Point& b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
double dz = a.z - b.z;
return dx * dx + dy * dy + dz * dz;
}
Node* build(vector<Point>& pts, int l, int r, int depth) {
if (l >= r) return nullptr;
int dim = depth % 3;
int mid = (l + r) / 2;
nth_element(pts.begin() + l, pts.begin() + mid, pts.begin() + r,
[&](const Point& a, const Point& b) {
if (dim == 0) return a.x < b.x;
if (dim == 1) return a.y < b.y;
return a.z < b.z;
});
Node* node = new Node(pts[mid]);
node->left = build(pts, l, mid, depth + 1);
node->right = build(pts, mid + 1, r, depth + 1);
return node;
}
void nearest(Node* node, const Point& target, int depth,
Point& best, double& bestDist) {
if (!node) return;
double d = dist2(node->p, target);
if (d < bestDist) {
bestDist = d;
best = node->p;
}
int dim = depth % 3;
Node *nearChild, *farChild;
if ((dim == 0 && target.x < node->p.x) ||
(dim == 1 && target.y < node->p.y) ||
(dim == 2 && target.z < node->p.z)) {
nearChild = node->left;
farChild = node->right;
} else {
nearChild = node->right;
farChild = node->left;
}
nearest(nearChild, target, depth + 1, best, bestDist);
double diff = (dim == 0) ? target.x - node->p.x
: (dim == 1) ? target.y - node->p.y
: target.z - node->p.z;
if (diff * diff < bestDist) {
nearest(farChild, target, depth + 1, best, bestDist);
}
}
int main() {
vector<Point> points = {
{2,3,4}, {5,4,2}, {9,6,7},
{4,7,9}, {8,1,5}, {7,2,6}
};
Node* root = build(points, 0, points.size(), 0);
Point target{9, 2, 6};
Point best;
double bestDist = 1e18;
nearest(root, target, 0, best, bestDist);
cout << "Nearest point: ("
<< best.x << ", "
<< best.y << ", "
<< best.z << ")\n";
cout << "Squared distance: " << bestDist << "\n";
return 0;
}

View File

@@ -0,0 +1,325 @@
# 1⃣ What is a K-D Tree (clean & precise)
A **K-D Tree (k-dimensional tree)** is a **binary tree for organizing points in k-dimensional space**.
Each node:
* Stores **one point** `(x₀, x₁, …, xₖ₋₁)`
* Splits space using **one dimension**
* Left/right child represent **two half-spaces**
### Core rule (the soul of KD-Tree)
At depth `d`:
```
split_dimension = d % k
```
* Left subtree: points with smaller coordinate on that dimension
* Right subtree: points with larger coordinate on that dimension
So the split dimension **cycles**:
```
2D: x → y → x → y → ...
3D: x → y → z → x → ...
```
---
## Why KD-Tree exists (not just “cool BST”)
KD-Tree is designed for **spatial queries**, not sorting.
It excels at:
* Nearest Neighbor (NN)
* k-Nearest Neighbor (kNN)
* Range / box queries
Why?
➡️ Because it **partitions space**, allowing **whole subtrees to be pruned**.
---
## Balanced construction (important)
Given all points beforehand:
1. Choose split dimension
2. Sort points by that dimension
3. Take **median** as root
4. Recurse on left / right
This gives:
* Height ≈ `O(log n)`
* Good pruning behavior
This is the version well implement.
---
## Geometry intuition (very important)
* Each node represents a **hyperplane**
* Each subtree represents a **region of space**
* Distance queries:
* First go to the “likely” side
* Then check if the other side **can possibly contain a closer point**
Thats the entire NN trick.
---
# 2⃣ 2D KD-Tree (C++ Template)
### Supported:
* Build from points
* Nearest Neighbor query
---
### Data structures
```cpp
#include <bits/stdc++.h>
using namespace std;
struct Point {
double x, y;
};
struct Node {
Point p;
Node *left, *right;
Node(Point _p) : p(_p), left(nullptr), right(nullptr) {}
};
```
---
### Utility functions
```cpp
double dist2(const Point& a, const Point& b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return dx * dx + dy * dy;
}
```
---
### Build KD-Tree (2D)
```cpp
Node* build(vector<Point>& pts, int l, int r, int depth) {
if (l >= r) return nullptr;
int dim = depth % 2;
int mid = (l + r) / 2;
nth_element(pts.begin() + l, pts.begin() + mid, pts.begin() + r,
[&](const Point& a, const Point& b) {
return dim == 0 ? a.x < b.x : a.y < b.y;
}
);
Node* node = new Node(pts[mid]);
node->left = build(pts, l, mid, depth + 1);
node->right = build(pts, mid + 1, r, depth + 1);
return node;
}
```
📌 `nth_element` gives **median in linear time** (better than full sort).
---
### Nearest Neighbor Search (2D)
```cpp
void nearest(Node* node, const Point& target, int depth,
Point& best, double& bestDist) {
if (!node) return;
double d = dist2(node->p, target);
if (d < bestDist) {
bestDist = d;
best = node->p;
}
int dim = depth % 2;
Node *nearChild, *farChild;
if ((dim == 0 && target.x < node->p.x) ||
(dim == 1 && target.y < node->p.y)) {
nearChild = node->left;
farChild = node->right;
} else {
nearChild = node->right;
farChild = node->left;
}
nearest(nearChild, target, depth + 1, best, bestDist);
double diff = (dim == 0)
? target.x - node->p.x
: target.y - node->p.y;
if (diff * diff < bestDist) {
nearest(farChild, target, depth + 1, best, bestDist);
}
}
```
💡 **Key idea**:
Only search the “other side” if the splitting line is close enough.
---
### Usage example
```cpp
int main() {
vector<Point> pts = {{2,3}, {5,4}, {9,6}, {4,7}, {8,1}, {7,2}};
Node* root = build(pts, 0, pts.size(), 0);
Point target{9, 2};
Point best;
double bestDist = 1e18;
nearest(root, target, 0, best, bestDist);
cout << best.x << " " << best.y << endl;
}
```
---
# 3⃣ 3D KD-Tree (C++ Template)
### Changes from 2D:
* Add `z`
* `depth % 3`
* Distance formula extends naturally
---
### Structures
```cpp
struct Point3 {
double x, y, z;
};
struct Node3 {
Point3 p;
Node3 *left, *right;
Node3(Point3 _p) : p(_p), left(nullptr), right(nullptr) {}
};
```
---
### Distance
```cpp
double dist2(const Point3& a, const Point3& b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
double dz = a.z - b.z;
return dx*dx + dy*dy + dz*dz;
}
```
---
### Build (3D)
```cpp
Node3* build3(vector<Point3>& pts, int l, int r, int depth) {
if (l >= r) return nullptr;
int dim = depth % 3;
int mid = (l + r) / 2;
nth_element(pts.begin() + l, pts.begin() + mid, pts.begin() + r,
[&](const Point3& a, const Point3& b) {
if (dim == 0) return a.x < b.x;
if (dim == 1) return a.y < b.y;
return a.z < b.z;
}
);
Node3* node = new Node3(pts[mid]);
node->left = build3(pts, l, mid, depth + 1);
node->right = build3(pts, mid + 1, r, depth + 1);
return node;
}
```
---
### Nearest Neighbor (3D)
```cpp
void nearest3(Node3* node, const Point3& target, int depth,
Point3& best, double& bestDist) {
if (!node) return;
double d = dist2(node->p, target);
if (d < bestDist) {
bestDist = d;
best = node->p;
}
int dim = depth % 3;
double diff;
Node3 *nearChild, *farChild;
if ((dim == 0 && target.x < node->p.x) ||
(dim == 1 && target.y < node->p.y) ||
(dim == 2 && target.z < node->p.z)) {
nearChild = node->left;
farChild = node->right;
} else {
nearChild = node->right;
farChild = node->left;
}
nearest3(nearChild, target, depth + 1, best, bestDist);
diff = (dim == 0) ? target.x - node->p.x
: (dim == 1) ? target.y - node->p.y
: target.z - node->p.z;
if (diff * diff < bestDist) {
nearest3(farChild, target, depth + 1, best, bestDist);
}
}
```
---
# Final mental model (keep this)
> **KD-Tree = BST where comparison dimension rotates, and pruning is geometric.**
Once you understand:
* `depth % k`
* median build
* “check other side only if needed”
Youve mastered KD-Trees.

View File

@@ -0,0 +1,344 @@
# Scapegoat Tree (α-Scapegoat Tree)
## 1. Overview
A **Scapegoat Tree** is a type of **self-balancing Binary Search Tree (BST)** proposed by *Igal Galperin and Ronald L. Rivest (1993)*.
Unlike AVL Trees or Red-Black Trees, it **does not store balance information** (such as heights or colors) inside nodes. Instead, it maintains balance by **occasionally rebuilding subtrees** when imbalance is detected.
The key idea is:
> *Allow the tree to become temporarily unbalanced, but detect structural violations and rebuild only when necessary.*
This makes the Scapegoat Tree conceptually simple while still guaranteeing good asymptotic performance.
---
## 2. Balance Criterion
The tree is parameterized by a real constant:
[
\alpha \in \left(\frac{1}{2}, 1\right)
]
A subtree rooted at node `x` is **α-weight-balanced** if:
[
\max(|\text{left}(x)|, |\text{right}(x)|) \le \alpha \cdot |\text{subtree}(x)|
]
If this condition is violated, `x` is called a **scapegoat**.
---
## 3. Height Guarantee
Let `n` be the number of nodes in the tree.
The height of a Scapegoat Tree is guaranteed to be:
[
h \le \log_{1/\alpha}(n)
]
Thus:
* Search: **O(log n)** worst-case
* Insert: **O(log n)** amortized
* Delete: **O(log n)** amortized
---
## 4. Insertion Strategy
Insertion proceeds in two phases:
### 4.1 Normal BST Insertion
* Insert the key as in a standard BST.
* Track the depth `d` of the inserted node.
### 4.2 Violation Detection
If:
[
d > \log_{1/\alpha}(n)
]
then the tree may be unbalanced.
### 4.3 Scapegoat Identification
* Traverse upward from the inserted node.
* Find the **first ancestor** where the α-balance condition is violated.
* This node is the **scapegoat**.
### 4.4 Rebuilding
* Rebuild the entire subtree rooted at the scapegoat into a perfectly balanced BST.
* This restores global balance.
---
## 5. Deletion Strategy
Deletion is handled lazily:
1. Perform standard BST deletion.
2. Maintain:
* `n`: current node count
* `max_n`: maximum node count ever reached
3. If:
[
n < \alpha \cdot \text{max_n}
]
then:
* Rebuild the **entire tree**
* Set `max_n = n`
This avoids frequent rebalancing during small deletions.
---
## 6. Rebuilding a Subtree
Rebuilding consists of two steps:
1. **Flatten**
* Perform an inorder traversal of the subtree
* Store nodes in a sorted array
2. **Rebuild**
* Recursively construct a perfectly balanced BST from the array
This operation takes **O(k)** time for a subtree of size `k`.
---
## 7. Comparison with Other BSTs
| Tree Type | Balance Info | Rebalancing | Worst-case Height |
| ------------- | ------------ | --------------- | ------------------ |
| AVL | Height | Rotations | O(log n) |
| Red-Black | Color | Rotations | O(log n) |
| Splay | None | Access-based | Amortized O(log n) |
| **Scapegoat** | None | Subtree rebuild | O(log n) |
---
## 8. Advantages and Disadvantages
### Advantages
* Simple node structure
* No rotations
* Deterministic height bound
* Easy to implement correctly
### Disadvantages
* Rebuild cost can be high
* Worse constant factors than AVL/RB trees
* Not ideal for real-time systems
---
## 9. Typical Use Cases
* Educational purposes
* Systems where simplicity > constant factors
* Situations where rotations are undesirable
* Batch-heavy insert/delete workloads
---
# C++ Template: Scapegoat Tree
Below is a **minimal, clean, academic-style template**, suitable for learning and extension.
---
## 1. Node Definition
```cpp
struct Node {
int key;
Node *left, *right, *parent;
int size;
Node(int k)
: key(k), left(nullptr), right(nullptr), parent(nullptr), size(1) {}
};
```
---
## 2. Core Class Skeleton
```cpp
class ScapegoatTree {
private:
const double alpha = 0.75;
Node* root = nullptr;
int n = 0;
int max_n = 0;
```
---
## 3. Utility Functions
### 3.1 Subtree Size Maintenance
```cpp
int getSize(Node* x) {
return x ? x->size : 0;
}
void update(Node* x) {
if (x) {
x->size = getSize(x->left) + getSize(x->right) + 1;
}
}
```
---
### 3.2 Inorder Flatten
```cpp
void flatten(Node* x, std::vector<Node*>& arr) {
if (!x) return;
flatten(x->left, arr);
arr.push_back(x);
flatten(x->right, arr);
}
```
---
### 3.3 Build Balanced Tree
```cpp
Node* build(std::vector<Node*>& arr, int l, int r, Node* parent) {
if (l > r) return nullptr;
int m = (l + r) / 2;
Node* x = arr[m];
x->parent = parent;
x->left = build(arr, l, m - 1, x);
x->right = build(arr, m + 1, r, x);
update(x);
return x;
}
```
---
## 4. Rebuild Subtree
```cpp
void rebuild(Node* x) {
std::vector<Node*> nodes;
flatten(x, nodes);
Node* parent = x->parent;
Node* newSub = build(nodes, 0, nodes.size() - 1, parent);
if (!parent) {
root = newSub;
} else if (parent->left == x) {
parent->left = newSub;
} else {
parent->right = newSub;
}
}
```
---
## 5. Balance Check
```cpp
bool isBalanced(Node* x) {
return getSize(x->left) <= alpha * x->size &&
getSize(x->right) <= alpha * x->size;
}
```
---
## 6. Insertion
```cpp
void insert(int key) {
if (!root) {
root = new Node(key);
n = max_n = 1;
return;
}
Node* cur = root;
Node* parent = nullptr;
int depth = 0;
while (cur) {
parent = cur;
cur->size++;
if (key < cur->key)
cur = cur->left;
else
cur = cur->right;
depth++;
}
Node* x = new Node(key);
x->parent = parent;
if (key < parent->key)
parent->left = x;
else
parent->right = x;
n++;
max_n = std::max(max_n, n);
if (depth > std::log(n) / std::log(1.0 / alpha)) {
Node* y = x->parent;
while (y && isBalanced(y)) {
y = y->parent;
}
if (y) rebuild(y);
}
}
```
---
## 7. Search (Standard BST)
```cpp
Node* find(int key) {
Node* cur = root;
while (cur) {
if (key == cur->key) return cur;
if (key < cur->key) cur = cur->left;
else cur = cur->right;
}
return nullptr;
}
```
---
## 8. Notes for Extension
* Add deletion with global rebuild
* Support order-statistics (`k`-th element)
* Replace `int key` with templates
* Remove parent pointers using recursion

View File

@@ -0,0 +1,132 @@
#include <bits/stdc++.h>
using namespace std;
struct Node {
int key;
Node *left, *right, *parent;
int size;
Node(int k)
: key(k), left(nullptr), right(nullptr), parent(nullptr), size(1) {}
};
class ScapegoatTree {
private:
const double alpha = 0.75;
Node* root = nullptr;
int n = 0; // current node count
int max_n = 0; // historical maximum node count
int getSize(Node* x) {
return x ? x->size : 0;
}
void update(Node* x) {
if (x) x->size = getSize(x->left) + getSize(x->right) + 1;
}
void flatten(Node* x, vector<Node*>& arr) {
if (!x) return;
flatten(x->left, arr);
arr.push_back(x);
flatten(x->right, arr);
}
Node* build(vector<Node*>& arr, int l, int r, Node* parent) {
if (l > r) return nullptr;
int m = (l + r) / 2;
Node* x = arr[m];
x->parent = parent;
x->left = build(arr, l, m - 1, x);
x->right = build(arr, m + 1, r, x);
update(x);
return x;
}
void rebuild(Node* x) {
vector<Node*> nodes;
flatten(x, nodes);
Node* parent = x->parent;
Node* newSub = build(nodes, 0, nodes.size() - 1, parent);
if (!parent) root = newSub;
else if (parent->left == x) parent->left = newSub;
else parent->right = newSub;
}
bool isBalanced(Node* x) {
return getSize(x->left) <= alpha * x->size &&
getSize(x->right) <= alpha * x->size;
}
public:
void insert(int key) {
if (!root) {
root = new Node(key);
n = max_n = 1;
return;
}
Node* cur = root;
Node* parent = nullptr;
int depth = 0;
while (cur) {
parent = cur;
cur->size++;
if (key < cur->key) cur = cur->left;
else cur = cur->right;
depth++;
}
Node* x = new Node(key);
x->parent = parent;
if (key < parent->key) parent->left = x;
else parent->right = x;
n++;
max_n = max(max_n, n);
if (depth > log(n) / log(1.0 / alpha)) {
Node* y = x->parent;
while (y && isBalanced(y)) y = y->parent;
if (y) rebuild(y);
}
}
Node* find(int key) {
Node* cur = root;
while (cur) {
if (key == cur->key) return cur;
if (key < cur->key) cur = cur->left;
else cur = cur->right;
}
return nullptr;
}
void inorder(Node* x) {
if (!x) return;
inorder(x->left);
cout << x->key << " ";
inorder(x->right);
}
void print() { inorder(root); cout << endl; }
};
int main() {
ScapegoatTree tree;
vector<int> keys = {50, 20, 70, 10, 30, 60, 80, 25};
for (int k : keys) tree.insert(k);
cout << "Inorder traversal of Scapegoat Tree: ";
tree.print();
int query = 25;
Node* res = tree.find(query);
if (res) cout << "Found " << query << " in tree.\n";
else cout << query << " not found.\n";
return 0;
}

View File

@@ -0,0 +1,72 @@
#include <iostream>
#include <vector>
using namespace std;
const int MAXN = 100005;
long long tree[MAXN << 2];
long long lazy[MAXN << 2];
int a[MAXN];
//Renew ancestor node
void pushUp(int p){
//Use *sum* as an example
tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
//Renew children through LazyTag(DirtyBit)
void pushDown(int p, int l, int r){
if(lazy[p] != 0){
int mid = l + (r-l)/2;
//Change tree and array
//if lazy[p]!0, pass lazy to lazy[kids](*length), tree[kids] + lazy, lazy[p]=0
lazy[p << 1] += lazy[p];
tree[p << 1] += lazy[p] * (mid-l+1);
lazy[p << 1 | 1] += lazy[p];
tree[p << 1 | 1] += lazy[p] * (r-mid);
/*You *Should Not* Recursively pushDown
Controlled by void update() or void ll query()
Otherwise: *O(log n) -> O(n)*
*/
lazy[p] = 0;
}
}
void build(int p, int l, int r){
lazy[p] = 0;
if(l == r) {
tree[p] = a[l];
return;
//single node noneed to pushup
}
int mid = l + (r-l)/2;
build(p << 1, l, mid);
build(p << 1 | 1, mid+1, r);
pushUp(p);
}
// Update: In [L, R], each add v
void update(int L, int R, int v, int l, int r, int p){
//Fully covered by [l, r]
if (L <= l && r <= R) {
tree[p] += (long long)v * (r-l+1);
lazy[p] += v;
return;
}
//Unfully covered by [l, r]
pushDown(p, l, r);
int mid = l + (r-l)/2;
//Now Recursively Update
if (L <= mid) update(L, R, v, l, mid, p << 1);
if (mid > R) update(L, R, v, mid+1, r, p << 1 | 1);
pushUp(p);
}
long long query(int L, int R, int l, int r, int p){
if (L <= l && r <= R) return tree[p];
pushDown(p, l, r);
int mid = (l+r) >> 1;
long long res = 0;
if(L <= mid) res += query(L, R, l, mid, p << 1);
if(R > mid) res += query(L, R, mid+1, r, p << 1 | 1);
return res;
}
int main(){
int n;
return 0;
}

View File

@@ -0,0 +1,199 @@
# 🌳 Splay Tree — A Self-Adjusting Binary Search Tree
## 1. What is a Splay Tree?
A **Splay Tree** is a type of **self-adjusting Binary Search Tree (BST)**.
Unlike AVL or RedBlack Trees, it **does not maintain explicit balance rules**.
Instead, it follows one simple idea:
> **Every time a node is accessed, move it to the root using rotations.**
This operation is called **splaying**.
As a result, the tree dynamically reorganizes itself based on **access patterns**, not height constraints.
---
## 2. Core Idea: “Access → Root”
In a Splay Tree:
* Searching for a key
* Inserting a node
* Deleting a node
All end with the same operation:
> 🔁 **Splay the accessed node to the root**
This is done via a sequence of rotations:
* **Zig** (single rotation)
* **ZigZig** (double rotation, same direction)
* **ZigZag** (double rotation, opposite directions)
After splaying:
* The accessed node becomes the **root**
* Nodes “related” to it move closer to the top
* Frequently accessed nodes stay shallow
---
## 3. What Makes Splay Trees Special?
### 3.1 No Explicit Balance Condition
Splay Trees:
* Do **not** store height, color, or priority
* Do **not** rebalance on every insert/delete
* May look “unbalanced” at any moment
Yet, surprisingly:
> ✅ **All operations run in amortized (O(\log n)) time**
This is proven using amortized analysis (potential method).
---
### 3.2 Self-Adjusting Behavior
Splay Trees adapt automatically to access patterns:
* Recently accessed nodes become fast to access again
* Sequential access becomes extremely efficient
* “Hot” keys naturally move near the root
This gives Splay Trees several strong properties:
* **Working-set property**
* **Static optimality**
* **Dynamic finger property**
These properties are hard (or impossible) to guarantee with strictly balanced trees.
---
## 4. Splay Tree as an “Easy-to-Pivot” Tree
A very useful way to think about Splay Trees is:
> 🧠 **Any accessed key can instantly become a pivot of the entire tree**
Once a node `x` is splayed to the root:
* All keys `< x` are in the left subtree
* All keys `> x` are in the right subtree
This leads directly to powerful structural operations.
---
## 5. Split and Merge — The Real Power
### 5.1 Split Operation
**Split** divides a tree into two trees based on a key `k`:
* `Left`: all keys `≤ k`
* `Right`: all keys `> k`
In a Splay Tree, this is easy:
1. Search for `k` (or the closest node)
2. Splay it to the root
3. Detach its left or right subtree
Because access automatically moves nodes to the root, **split is almost free**.
---
### 5.2 Merge Operation
**Merge** combines two trees:
* All keys in `Left` are smaller than those in `Right`
Method:
1. Splay the **maximum node** of `Left` to the root
2. Attach `Right` as its right child
Again, splaying makes this simple and efficient.
---
## 6. Interval and Range Manipulation
By combining **split** and **merge**, Splay Trees can easily isolate any interval `[L, R]`:
```text
T
├─ split by R → A , C
└─ split A by L-1 → B , Mid
```
Now:
* `Mid` contains exactly the range `[L, R]`
* You can apply operations to `Mid` only
* Then merge everything back
This makes Splay Trees ideal for:
* Sequence manipulation
* Range updates
* Subarray operations
---
## 7. Typical Applications
### 7.1 Text Editors and Ropes
* Cursor moves locally
* Recent edits are reused
* Cut / paste / reverse ranges efficiently
### 7.2 Dynamic Trees (LinkCut Tree)
* Splay Tree is the **core data structure**
* Supports path queries and updates
* AVL / RedBlack Trees cannot replace it here
### 7.3 Cache-like Access Patterns
* Frequently accessed keys stay near the root
* No need to maintain explicit frequency counters
---
## 8. Comparison with Other BSTs
| Tree Type | Balance Method | Worst Case | Interval Ops | Adaptivity |
| --------- | --------------- | ---------------------- | ------------ | ---------- |
| AVL | Height | (O(\log n)) | Hard | ❌ |
| RedBlack | Color rules | (O(\log n)) | Hard | ❌ |
| Treap | Random priority | (O(\log n)) (expected) | Easy | ❌ |
| **Splay** | Access-based | (O(n)) (single op) | **Easy** | ✅ |
---
## 9. When NOT to Use Splay Trees
Splay Trees are **not ideal** when:
* Strict worst-case latency is required
* Access patterns are uniformly random
* Predictable structure is more important than adaptivity
In such cases, AVL or RedBlack Trees may be better.
---
## 10. One-Sentence Summary
> **A Splay Tree is a self-adjusting BST where any accessed node becomes the root, making the tree extremely easy to split, merge, and reorganize around chosen keys — with strong amortized performance guarantees.**

View File

@@ -0,0 +1,125 @@
#include <iostream>
#include <vector>
using namespace std;
struct Node {
int key;
Node *fa, *ch[2];
int sz;
Node(int k) : key(k), fa(nullptr), sz(1) {
ch[0] = ch[1] = nullptr;
}
};
inline int size(Node* x) {
return x ? x->sz : 0;
}
inline void pull(Node* x) {
if (x)
x->sz = 1 + size(x->ch[0]) + size(x->ch[1]);
}
inline bool is_right(Node* x) {
return x->fa && x->fa->ch[1] == x;
}
void rotate(Node* x) {
Node* p = x->fa;
Node* g = p->fa;
bool dir = (x == p->ch[1]); // 0 = left, 1 = right
// connect x's opposite child to p
p->ch[dir] = x->ch[dir ^ 1];
if (x->ch[dir ^ 1])
x->ch[dir ^ 1]->fa = p;
// move p under x
x->ch[dir ^ 1] = p;
p->fa = x;
// connect x to g
x->fa = g;
if (g) {
if (g->ch[0] == p) g->ch[0] = x;
else g->ch[1] = x;
}
pull(p);
pull(x);
}
void splay(Node* x, Node* goal = nullptr) {
while (x->fa != goal) {
Node* p = x->fa;
Node* g = p->fa;
if (g != goal) {
if ((g->ch[0] == p) == (p->ch[0] == x))
rotate(p); // zig-zig
else
rotate(x); // zig-zag
}
rotate(x);
}
}
/*
* Split by k, result: left <= k, right >= k
*/
void split(Node* root, int k, Node*& left, Node*& right) {
Node* cur = root;
Node* last = nullptr;
while (cur) {
last = cur;
if (k < cur->key)
cur = cur->ch[0];
else
cur = cur->ch[1];
}
if (last)
splay(last);
if (!last) {
left = right = nullptr;
return;
}
if (last->key <= k) {
left = last;
right = last->ch[1];
if (right) right->fa = nullptr;
left->ch[1] = nullptr;
pull(left);
} else {
right = last;
left = last->ch[0];
if (left) left->fa = nullptr;
right->ch[0] = nullptr;
pull(right);
}
}
Node* merge(Node* left, Node* right) {
if (!left) return right;
if (!right) return left;
// find max of left
Node* cur = left;
while (cur->ch[1])
cur = cur->ch[1];
splay(cur); // max becomes root
cur->ch[1] = right;
right->fa = cur;
pull(cur);
return cur;
}
int main(){
return 0;
}

View File

@@ -0,0 +1,67 @@
#include <bits/stdc++.h>
using namespace std;
struct Node {
int key, priority;
int left, right;
int size;
};
const int MAXN = 200000;
Node tr[MAXN];
int tot = 0;
int newNode(int key) {
++tot;
tr[tot] = {key, rand(), 0, 0, 1};
return tot;
}
int getSize(int x) {
return x ? tr[x].size : 0;
}
void pushUp(int x) {
tr[x].size = getSize(tr[x].left) + getSize(tr[x].right) + 1;
}
// Split by key: left <= key, right > key
void split(int root, int key, int &x, int &y) {
if (!root) {
x = y = 0;
return;
}
if (tr[root].key <= key) {
x = root;
split(tr[root].right, key, tr[root].right, y);
pushUp(x);
} else {
y = root;
split(tr[root].left, key, x, tr[root].left);
pushUp(y);
}
}
int merge(int x, int y) {
if (!x || !y) return x | y;
if (tr[x].priority > tr[y].priority) {
tr[x].right = merge(tr[x].right, y);
pushUp(x);
return x;
} else {
tr[y].left = merge(x, tr[y].left);
pushUp(y);
return y;
}
}
// Insert key into treap
void insert(int &root, int key) {
int x, y;
split(root, key, x, y);
root = merge(merge(x, newNode(key)), y);
}
int main(){
return 0;
}

View File

@@ -0,0 +1,283 @@
# Treap: A Randomized Balanced Binary Search Tree
## 1. Overview
A **Treap** (Tree + Heap) is a **randomized balanced binary search tree** that simultaneously satisfies:
1. **Binary Search Tree (BST) property** on keys
2. **Heap property** on priorities
By assigning each node a random priority, Treap achieves **expected logarithmic height**, providing efficient dynamic set and sequence operations.
Treaps are widely used in:
* Dynamic ordered sets
* Sequence maintenance
* Range query problems
* Competitive programming as a flexible alternative to AVL / Red-Black trees
---
## 2. Structural Properties
Each Treap node contains:
* `key`: used to maintain BST ordering
* `priority`: a randomly assigned value
* `left`, `right`: child pointers (or indices)
* Optional augmented data (e.g., subtree size, sum)
### 2.1 BST Property
For any node `u`:
* All keys in `u.left` are **less than** `u.key`
* All keys in `u.right` are **greater than** `u.key`
### 2.2 Heap Property
For any node `u`:
* `priority(u)` is **greater than or equal to** the priorities of its children
(Max-heap convention; min-heap also works symmetrically)
---
## 3. Why Randomization Works
If priorities are independent random variables, then:
* The expected height of the Treap is **O(log n)**
* All standard BST operations run in **expected O(log n)** time
This avoids the need for strict rebalancing rules, unlike AVL or Red-Black trees.
---
## 4. Implementation Models
In practice, Treaps are implemented as **pointer-based trees**.
However, for efficiency and memory safety, most implementations use:
> **Array-based node pools with integer indices simulating pointers**
This approach:
* Avoids frequent dynamic memory allocation
* Improves cache locality
* Is standard in algorithmic contexts
---
## 5. Rotating Treap (Classic Treap)
### 5.1 Core Idea
Insertion proceeds as in a normal BST by `key`.
If the heap property is violated after insertion, **tree rotations** are used to restore it.
### 5.2 Operations
* Insert: BST insert + rotations
* Delete: rotate node down until removable
* Search: standard BST search
### 5.3 Characteristics
**Advantages**
* Conceptually close to AVL / Red-Black trees
* Intuitive if rotations are already familiar
**Disadvantages**
* Rotation logic can be error-prone
* Slightly harder to extend for sequence problems
---
## 6. FHQ Treap (Split & Merge Treap)
### 6.1 Core Idea
The FHQ Treap (named after its proposer) eliminates rotations entirely.
It relies on two fundamental operations:
1. **Split**
* Divide a Treap into two based on a key (or position)
2. **Merge**
* Combine two Treaps assuming all keys in the left are smaller
All operations are expressed using **split + merge**, preserving both BST and heap properties automatically.
---
### 6.2 Why FHQ Treap Is Popular
* No explicit rotations
* Cleaner and more modular code
* Ideal for **implicit Treap** (sequence problems)
* Easier to augment with lazy propagation
As a result, FHQ Treap is the **dominant Treap variant** in competitive programming.
---
## 7. Comparison Summary
| Aspect | Rotating Treap | FHQ Treap |
| ---------------- | -------------- | ------------- |
| Balancing method | Rotations | Split & Merge |
| Code complexity | Medium | Low |
| Extensibility | Moderate | Excellent |
| Sequence support | Harder | Natural |
| Contest usage | Less common | Very common |
---
## 8. Rotating Treap — C++ Template
```cpp
#include <bits/stdc++.h>
using namespace std;
struct Node {
int key, priority;
int left, right;
int size;
};
const int MAXN = 200000;
Node tr[MAXN];
int tot = 0;
int root = 0;
int newNode(int key) {
++tot;
tr[tot] = {key, rand(), 0, 0, 1};
return tot;
}
int getSize(int x) {
return x ? tr[x].size : 0;
}
void pushUp(int x) {
tr[x].size = getSize(tr[x].left) + getSize(tr[x].right) + 1;
}
void rotateLeft(int &x) {
int y = tr[x].right;
tr[x].right = tr[y].left;
tr[y].left = x;
pushUp(x);
pushUp(y);
x = y;
}
void rotateRight(int &x) {
int y = tr[x].left;
tr[x].left = tr[y].right;
tr[y].right = x;
pushUp(x);
pushUp(y);
x = y;
}
void insert(int &x, int key) {
if (!x) {
x = newNode(key);
return;
}
if (key < tr[x].key) {
insert(tr[x].left, key);
if (tr[tr[x].left].priority > tr[x].priority)
rotateRight(x);
} else {
insert(tr[x].right, key);
if (tr[tr[x].right].priority > tr[x].priority)
rotateLeft(x);
}
pushUp(x);
}
```
---
## 9. FHQ Treap — C++ Template
```cpp
#include <bits/stdc++.h>
using namespace std;
struct Node {
int key, priority;
int left, right;
int size;
};
const int MAXN = 200000;
Node tr[MAXN];
int tot = 0;
int newNode(int key) {
++tot;
tr[tot] = {key, rand(), 0, 0, 1};
return tot;
}
int getSize(int x) {
return x ? tr[x].size : 0;
}
void pushUp(int x) {
tr[x].size = getSize(tr[x].left) + getSize(tr[x].right) + 1;
}
// Split by key: left <= key, right > key
void split(int root, int key, int &x, int &y) {
if (!root) {
x = y = 0;
return;
}
if (tr[root].key <= key) {
x = root;
split(tr[root].right, key, tr[root].right, y);
pushUp(x);
} else {
y = root;
split(tr[root].left, key, x, tr[root].left);
pushUp(y);
}
}
int merge(int x, int y) {
if (!x || !y) return x | y;
if (tr[x].priority > tr[y].priority) {
tr[x].right = merge(tr[x].right, y);
pushUp(x);
return x;
} else {
tr[y].left = merge(x, tr[y].left);
pushUp(y);
return y;
}
}
// Insert key into treap
void insert(int &root, int key) {
int x, y;
split(root, key, x, y);
root = merge(merge(x, newNode(key)), y);
}
```
---
## 10. Closing Remarks
Treap combines the simplicity of BSTs with the robustness of randomized balancing.
Among its variants, **FHQ Treap** stands out for its elegance, extensibility, and practical usefulness, especially in sequence-based problems.

View File

@@ -0,0 +1,67 @@
#include <bits/stdc++.h>
using namespace std;
struct Node {
int key, priority;
int left, right;
int size;
};
const int MAXN = 200000;
Node tr[MAXN];
int tot = 0;
int root = 0;
int newNode(int key) {
++tot;
tr[tot] = {key, rand(), 0, 0, 1};
return tot;
}
int getSize(int x) {
return x ? tr[x].size : 0;
}
void pushUp(int x) {
tr[x].size = getSize(tr[x].left) + getSize(tr[x].right) + 1;
}
void rotateLeft(int &x) {
int y = tr[x].right;
tr[x].right = tr[y].left;
tr[y].left = x;
pushUp(x);
pushUp(y);
x = y;
}
void rotateRight(int &x) {
int y = tr[x].left;
tr[x].left = tr[y].right;
tr[y].right = x;
pushUp(x);
pushUp(y);
x = y;
}
void insert(int &x, int key) {
if (!x) {
x = newNode(key);
return;
}
if (key < tr[x].key) {
insert(tr[x].left, key);
if (tr[tr[x].left].priority > tr[x].priority)
rotateRight(x);
} else {
insert(tr[x].right, key);
if (tr[tr[x].right].priority > tr[x].priority)
rotateLeft(x);
}
pushUp(x);
}
int main(){
return 0;
}