Skip to content

Commit

Permalink
finished splay tree (excluding proofs)
Browse files Browse the repository at this point in the history
  • Loading branch information
ibpjbl authored Jul 27, 2019
1 parent ff48b57 commit 8469c64
Showing 1 changed file with 152 additions and 0 deletions.
152 changes: 152 additions & 0 deletions link_cut.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,155 @@
По сути, splay-дерево - это детерминированное [декартово дерево](https://algorithmica.org/ru/treap) с амортизированным временем работы (из-за чего нельзя эффективно сделать splay-дерево персистентным). Базовая функция для работы splay-дерева - $find(x)$ - находит вершину $u$ с максимальным ключом, не большим $x$. Реализуется $find(x)$ так же, как и в других деревьях поиска - обычным спуском, но после обязательно нужно выполнить $expose(u)$. Через $find(x)$ реализуются операции $split$ и $merge$:
+ $split(x, k)$ - так же, как и в декартовом дереве, принимает исходное дерево $x$ и возвращает два дерева $l$ и $r$, причём все ключи в $l$ меньше $k$, а все ключи в $r$ - не меньше $k$. Выполним операцию $find(k)$ в дереве $x$, после которой найденная вершина $u$ окажется корнем $x$; заметим, что тогда ключи всех вершин в поддереве левого сына $u$ (обозначим его за $l$) будут меньше $k$, то есть мы можем "отрезать" левое поддерево от $u$ и вернуть пару ${l, u}$.
+ $merge(l, r)$ - принимает два дерева $l$ и $r$ (причём все ключи в $l$ меньше всех ключей в $r$) и возвращает новое дерево $x$, состоящее из всех вершин $l$ и $r$. При помощи операции $find$ найдём в $r$ вершину с минимальным ключом, после чего она станет корнем $r$; заметим, что у неё не будет левого сына, тогда мы просто сделаем этим левым сыном всё дерево $l$.
Остальные операции реализуются через эти три (например, $add(x)$, $remove(x)$ и т.д).

## Реализация $expose(v)$

Если реализовать $expose(v)$ простой серией поворотов $rotate(v)$ (пока $v$ не станет корнем), то можно подобрать пример, когда она всегда будет отрабатывать за $O(n)$ (например, если дерево - это один путь, подвешенный за конец, и $find$ каждый раз затрагивает другой конец пути). Для достижения асимптотики $\hat{O}(n\,log\,n)$ при реализации $expose(v)$ используются три вспомогательные операции - *zig*, *zig-zig* и *zig-zag* - которые являются комбинациями операций $rotate$.

### $zig-zig(v)$

Обозначим за $p(x)$ предка вершины $x$, а за $L(x)$ - функцию, возвращающую 0, если $x$ - левый сын $p(x)$ или $p(x)$ не существует, и 1, если $x$ - правый сын $p(x)$. Операция $zig-zig(v)$ применяется в том случае, если $v$ не является непосредственным сыном корня, и $L(x)=L(p(x))$. $zig-zig(v)$ состоит из двух операций - $rotate(v)$ и $rotate(w)$ (где $w$ - предок $v$ до выполнения $zig-zig(v)$) - которые выполняются именно в таком порядке.

### $zig-zag(v)$

Операция $zig-zag(v)$ применяется в том случае, если $v$ не является непосредственным сыном корня, и $L(x)\neq L(p(x))$. $zig-zig(v)$ состоит из двух операций - $rotate(w)$ и $rotate(v)$ (где $w$ - предок $v$ до выполнения $zig-zag(v)$) - которые выполняются именно в таком порядке.

### $zig(v)$

Операция $zig(v)$ применяется в том случае, если $v$ является непосредственным сыном корня, и представляет собой одну операцию $rotate(v)$.

### Итоговый алгоритм

Таким образом, для реализации $expose(v)$ мы просто в цикле до тех пор, пока $v$ не станет корнем, рассматриваем три случая:
1. $v$ - непосредственный сын корня. Тогда выполняем $zig(v)$.
2. $L(v) = L(p(v))$ - тогда выполняем $zig-zig(v)$.
3. $L(v) \neq L(p(v)$ - тогда выполняем $zig-zag(v)$.

## Неявное splay-дерево

Давайте считать, что splay-дерево хранит некоторый массив, причём все элементы массива, за которые отвечают вершины в левом поддереве $x$, находятся левее элемента $x$, а все элементы в правом поддереве - правее $x$ - как в декартовом дереве по неявному ключу. Тогда $find(k)$ будет искать в дереве вершину, отвечающую за $k$-й слева элемент этого массива. Таким образом, неявное splay-дерево решает те же задачи, что и неявное декартово дерево. На практике splay-дерево обычно оказывается в несколько раз быстрее, чем декартово дерево, из-за малой константы (см. время работы), поэтому если не получается упихать декартово дерево, есть много времени и опыт написания splay-дерева, то можно попытаться использовать его (однако, опять же, персистентность прикрутить не получится).

## Реализация

```c++
struct node {
int x, sz = 1;
int p = -1, l = -1, r = -1;
bool lft = 0;

node() {}

node(int x) : x(x) {}
};

node v[maxn];
int root = -1, mx = 0;

int gsz(int x) {
return (x == -1 ? 0 : v[x].sz);
}

void upd(int x) {
v[x].sz = 1 + gsz(v[x].l) + gsz(v[x].r);
}

// Отсоединяет вершину от предка, обновляя необходимые параметры

void disconnect(int x) {
if (x == -1 || v[x].p == -1) return;
if (v[v[x].p].l == x) v[v[x].p].l = -1;
else v[v[x].p].r = -1;
upd(v[x].p); v[x].p = -1; v[x].lft = 0;
}

// Делает вершину x левым или правым (в зависимости от lft) сыном y

void connect(int x, int y, bool lft) {
if (x == -1 || y == -1) return;
v[x].lft = lft; v[x].p = y;
if (lft) v[y].l = x;
else v[y].r = x;
upd(y);
}

void rotate(int x) {
int y = v[x].p, z = v[y].p, yl = v[y].lft, xl = v[x].lft;
disconnect(x); disconnect(y);
if (xl) {
int b = v[x].r;
disconnect(b); connect(b, y, 1); connect(y, x, 0);
} else {
int b = v[x].l;
disconnect(b); connect(b, y, 0); connect(y, x, 1);
}
connect(x, z, yl);
}

// Если вместо expose написать splay, то больше людей поймут, что вы написали splay-дерево, посмотрев код посылки на кф

void splay(int x) {
if (x == -1) return;
while (v[x].p != -1) {
int y = v[x].p;
if (v[y].p == -1) {
rotate(x);
break;
}
if (v[x].lft == v[y].lft) {
rotate(y);
rotate(x);
} else {
rotate(x);
rotate(x);
}
}
root = x;
}

// Функция find, которую я зачем-то назвал get

int get(int x, int k) {
if (x == -1) return -1;
while (1) {
if (gsz(v[x].l) == k)
break;
if (k < gsz(v[x].l)) {
x = v[x].l;
} else {
k -= (gsz(v[x].l) + 1);
x = v[x].r;
}
}
splay(x);
return x;
}

pair<int, int> split(int x, int k) {
if (x == -1) return {-1, -1};
if (k == 0) return {-1, x};
int aut = get(x, k - 1);
int gg = v[aut].r;
disconnect(gg);
return {aut, gg};
}

int merge(int l, int r) {
if (l == -1) return r;
if (r == -1) return l;
int bs = gsz(l);
int aut = get(l, bs - 1);
connect(r, aut, 0);
return aut;
}
```
Это первая и последняя реализация splay-дерева, которую я писал, поэтому код такой длинный. Самое болезненное - дебагать обновление параметров вершин при переподвешивании.
## Время работы
Доказательство асимптотики длинное и использует метод потенциалов, допишу потом TODO
# Link-cut tree
TODO

0 comments on commit 8469c64

Please sign in to comment.