LCA офлайн: алгоритм Тарьяна
Если все запросы известны заранее, LCA считается одним обходом и системой непересекающихся множеств — почти за линию.
3 мин
Все предыдущие способы работают онлайн: запрос пришёл — ответили. Если запросы даны заранее, есть способ дешевле.
Что нужно знать
Алгоритм опирается на систему непересекающихся множеств (СНМ, DSU) — структуру, которая хранит разбиение элементов на группы и умеет две операции: узнать представителя группы элемента и объединить две группы. С эвристиками сжатия пути и объединения по рангу обе стоят , где — обратная функция Аккермана: величина, не превосходящая 4 для любых мыслимых .
Ниже используется минимальный интерфейс: find(v) и «прилить группу к группе ».
Идея
Идём обходом в глубину. В момент, когда мы стоим в вершине , все уже пройденные вершины разбиты на группы: для каждого предка — своя группа из тех вершин его поддерева, которые обход уже покинул.
Свойство групп: для всех вершин одной группы ответ на запрос с одинаков — это тот предок, за которым группа закреплена.
Поэтому, зайдя в , мы можем ответить на все запросы , где уже посещена: находим представителя группы и берём закреплённый за ним ответ.
Группы не пересекаются — значит, их можно держать в СНМ.
Код
void dfs(int v, int p) {
dsu[v] = v;
anc[v] = v; // за группой v пока закреплена сама v
for (int u : g[v]) if (u != p) {
dfs(u, v);
dsu[find(u)] = v; // приливаем группу ребёнка
anc[find(v)] = v; // и переназначаем ответ на v
}
used[v] = true;
for (auto [u, id] : queries[v])
if (used[u]) answer[id] = anc[find(u)];
}
Порядок строк существенен. Пометка used[v] ставится после обхода детей, но до ответа на запросы: иначе запрос вида обработается неверно.
Строка anc[find(v)] = v нужна каждый раз после приливания: find мог поменять представителя, и ответ надо переназначить на новую голову.
Каждый запрос записывается в список обеих своих вершин; отвечаем на нём тогда, когда дошли до второй.
Проверено: 30 000 деревьев, 2 177 536 запросов — совпало с подъёмом по родителям.
Сложность
— практически линия. Ни один онлайн-способ такого не даёт.
Плата — офлайн: все запросы должны быть известны до начала работы.
Какой способ когда
| способ | предподсчёт | память | запрос | онлайн |
|---|---|---|---|---|
| подъём по родителям | да | |||
| двоичные подъёмы | да | |||
| прыжковые указатели | да | |||
| эйлеров обход + дерево отрезков | да | |||
| эйлеров обход + sparse table | да | |||
| Тарьян | нет |
Практическое правило:
- по умолчанию — двоичные подъёмы: пишутся быстрее всего и попутно дают функции на пути и Level Ancestor;
- очень много запросов ( и выше) — эйлеров обход с sparse table;
- жёсткий лимит памяти — прыжковые указатели или эйлеров обход с деревом отрезков;
- запросы даны заранее и их очень много — Тарьян.
В контестах по этой теме ограничения обычно подбирают так, чтобы разные задачи требовали разных способов. Поэтому знать полезно все.