---
tags: algorithm
---
# Treap (fhq)
---
## 原理
在維持`heap`的同時,做出`平衡二元樹`的操作
:::spoiler heap
:::info
父親的值恆小於(或大於)等於孩子的值
:::
:::spoiler 問題
:::info
Q:二元樹不是右節點的值要大於節點的值??
A:所以另增另外一個值`pri`來保持`heap`的值
:::
---
## 節點
```cpp=
mt19937 rd;
struct node {
node *l, *r;
int pri,val;
node(int k) : pri(rd()), val(k), sz(1) {l = r = nullptr;}
}
```
---
$$每一個$$
```graphviz
digraph main {
1[label ="node"]
}
```
$$都視為一個treap$$
<p hidden>
</p>
---
## 主要操作
### merge
- `merge(node *a ,node *b)`
$\implies$ 將兩個`node`,`a`,`b`合併。
:::danger
先決條件: `b` 的每一個元素皆大於等於`a`
:::
複雜度$O(\log N)$
### spilt
- `spilt(node *cur, int k, node *& a, node *& b)`
$\implies$ 將`cur`中值小於`k`的節點存到`a`, 大於等於`k`的則存到`b`。
:::danger
因為要存下節點,所以`a`, `b`要加參照
:::
複雜度$O(\log N)$
```graphviz
digraph main {
treap -> {"<k", ">=k"}[label = spilt]
{"<k", ">=k"} -> "new treap"[label = merge]
}
```
```cpp=
node* t_merge(node *a, node *b) { //b ele > a ele, and maxium heap
if(!a || !b) return (a ? a : b);
if(a -> pri > b -> pri)
return a -> r = t_merge(a -> r, b),a -> pull(), a;
else
return b -> l = t_merge(a, b -> l), b -> pull(), b;
}
void spilt(node *cur, node *& a, node *& b, int k) { //a ele < k, b ele >= k
if(!cur) {a = b = NULL; return;}
if(cur -> val < k) {
a = cur;
spilt(cur -> r, a -> r, b, k);
}
else {
b = cur;
spilt(cur -> l, a, b -> l, k);
}
}
```
---
## 基礎操作
### insert
- `insert(int k)`
#### 想法: 先把`treap`一分為二(< k 跟 >= k),在把node(k)`merge`回去即可
```graphviz
digraph main{
1[label = ">=k"]
"cur" -> "<k"[label = spilt]
"cur" -> ">=k"[label = spilt]
{">=k", "k"} -> 1[label = merge];
{"<k", 1} -> "new treap"[label = merge]
{rank = same; "<k", ">=k"}
}
```
``` cpp =
void t_insert(node *&cur, int k) {
node *a, *b;
a = b = NULL;
spilt(cur, a, b, k);
cur = t_merge(a, t_merge(new node(k), b));
}
void t_insert(int k) {t_insert(root, k);}
```
### earse
- `earse(int k)`
#### 想法: 把`treap`一分為二, 把(>=k)的部分在分一次(==k 跟 > k),把(<k)跟(>k)的merge起來即可
```graphviz
digraph main {
1[label = cur]
2[label = "< k"]
3[label = " >= k"]
4[label = "== k"]
5[label = ">k"]
6[label = "treap with out k"]
1 -> {2, 3}[label = spilt];
3 -> {4,5}[label = spilt]
{2, 5} -> 6[label = merge]
{rank = same; 2, 3}
}
```
---
## 特別的操作
### sspilt
-`sspilt(node *cur, int k, node *a, node *b)`
$\implies$ 將`cur`一分為二,`size(a) = k`, `size(b) = size(cur) - k`
```graphviz
digraph {
a[label = "size N"]
a -> "size < k", "size >= k"[label = ssplit]
"size < k", "size >= k" -> "size N"[label = merge]
}
```
不過這樣就要維護子節點的`size`,要再多存一些值。
```cpp=
mt19937 rd;
struct node {
node *l, *r;
int pri,val, sz;
node(int k) : pri(rd()), val(k), sz(1) {l = r = nullptr;}
void pull() {sz = (l ? l -> sz :0) + (r ? r -> sz : 0)+ 1;}
}
```
```cpp=
int cc_sz(node* cur) {return (cur ? cur -> sz : 0);}
void sspilt(node *cur, node *& a, node *& b, int sz) { // a.sz = sz, b.sz else
if(!cur) {a = b = NULL; return;}
if(cc_sz(cur -> l) + 1 <= sz) {
a = cur;
sz -= cc_sz(cur -> l) + 1;
sspilt(cur -> r, a -> r, b, sz);
a -> pull();
}
else {
b = cur;
sspilt(cur -> l, a, b -> l, sz);
b -> pull();
}
}
```
### 序列第k小
```cpp=
int k_th(node *& cur, int rnk) {
node *a, *b, *c;
a = b = c = NULL;
sspilt(cur, a, b ,rnk);
sspilt(a, d, c, rnk - 1);
int ans = c -> val;
cur = t_merge(t_merge(d, c), b);
return ans;
}
int k_th(int rnk) {return k_th(root, rnk);}
```
## 實作注意:
- 節點的`size`是左子樹加右子樹在加1
- 用`int size(node *cur)`來確認節點的`size`,以免取到`nullptr`
- 在`spilt`和`sspilt`時,當節點`cur`為`nullptr`時記得把`a, b`設成`nullptr`
- 如果有`spilt`或`sspilt`記得把節點`merge`回去
## 延伸應用
平衡二元樹還有另一個神奇的作用,可以做線段操作!!!
而且直覺了許多,你可以把`treap`當成一個序列,把id當成搜尋的對象的話,你就可以用`split`來去分割這個序列。
```cpp=
struct node {
node *l, *r;
int id, val, pri, sum;
node(int k) : val(k), pri(rd()), sz(1), sum(k) {l = r = NULL};
void pull() {sum = (l ? l -> sum : 0) + (r ? r -> sum : 0) + val;}
};
```
### 區間和
如果你要求`[l, r]`區間和的話只要把`treap`分成三份
```graphviz
digraph {
"[1, N]" -> "[1, L\-1]", "[L, N]"
"[L, N]" -> "[L, R]", "[R-1, N]"
}
```
最後在合併起來就好了
### 單點修改
就分出一個節點在`merge`回去就可以了。
### 區間修改
加上懶標記
### 區間翻轉
一樣是懶標記,遇到懶標記時把左右子樹交換即可。
```cpp=
mt19937 rd;
struct treap {
struct node {
node *l, *r;
int val, pri;
int sz;
node(int k) : val(k), pri(rd()), sz(1) {l = r = nullptr;}
void pull() {sz = (l ? l -> sz : 0) + (r ? r -> sz : 0) + 1;}
}*root = NULL;
int cc_sz(node* cur) {return (cur ? cur -> sz : 0);}
node* t_merge(node *a, node *b) { //b ele > a ele, and maxium heap
if(!a || !b) return (a ? a : b);
if(a -> pri > b -> pri)
return a -> r = t_merge(a -> r, b),a -> pull(), a;
else
return b -> l = t_merge(a, b -> l), b -> pull(), b;
}
void spilt(node *cur, node *& a, node *& b, int k) { //a ele < k, b ele >= k
if(!cur) {a = b = NULL; return;}
if(cur -> val < k) {
a = cur;
spilt(cur -> r, a -> r, b, k);
a -> pull();
}
else {
b = cur;
spilt(cur -> l, a, b -> l, k);
b -> pull();
}
}
void sspilt(node *cur, node *& a, node *& b, int sz) { // a.sz = sz, b.sz else
if(!cur) {a = b = NULL; return;}
if(cc_sz(cur -> l) + 1 <= sz) {
a = cur;
sz -= cc_sz(cur -> l) + 1;
sspilt(cur -> r, a -> r, b, sz);
a -> pull();
}
else {
b = cur;
sspilt(cur -> l, a, b -> l, sz);
b -> pull();
}
}
int k_th(node *& cur, int rnk) {
node *a, *b, *c, *d;
a = b = c = NULL;
sspilt(cur, a, b ,rnk);
sspilt(a, d, c, rnk - 1);
int ans = c -> val;
cur = t_merge(t_merge(d, c), b);
return ans;
}
int k_th(int rnk) {return k_th(root, rnk);}
void t_insert(node *&cur, int k) {
node *a, *b;
a = b = NULL;
spilt(cur, a, b, k);
cur = t_merge(t_merge(a, new node(k)), b);
}
void t_insert(int k) {t_insert(root, k);}
void t_print(node *cur) {
if(!cur)return;
t_print(cur -> l);
de(cur -> val, cur -> sz);
t_print(cur -> r);
}
void t_print() {t_print(root);}
};
```