Алгоритм Тарьяна поиска LCA за O(1) в оффлайн — различия между версиями
Shersh (обсуждение | вклад) (→Реализация) |
Shersh (обсуждение | вклад) (→Алгоритм) |
||
Строка 10: | Строка 10: | ||
На рисунке разные цвета {{---}} разные классы, а белые вершины ещё не просмотренные в <tex>dfs</tex>. | На рисунке разные цвета {{---}} разные классы, а белые вершины ещё не просмотренные в <tex>dfs</tex>. | ||
− | Классы этих вершин не пересекаются, а значит мы их | + | [[file:mytree.png|500px|разные цвета {{---}} разные классы, а белые вершины ещё не просмотренные в dfs]] |
+ | |||
+ | Классы этих вершин не пересекаются, а значит мы можем их эффективно обрабатывать с помощью [[СНМ (реализация с помощью леса корневых деревьев)|системы непересекающихся множеств]], которую будем хранить в массиве <tex>dsu</tex>. | ||
+ | |||
+ | Будем поддерживать массив <tex>ancestor[1 \dots n]</tex>, где <tex> ancestor[w] </tex> {{---}} наименьший общий предок всех вершин, которые лежат в том же классе, что и <tex> w </tex>. Обновление массива <tex> ancestor </tex> для каждого элемента будет неэффективно. Поэтому зафиксируем в каждом классе какого-то представителя этого класса. Функция <tex> \mathrm{find}(w) </tex> вернёт представителя класса, в котором находится вершина <tex> w </tex>. Тогда наименьшим общим предком всех вершин из класса <tex> w </tex> будет вершина <tex> ancestor[\mathrm{find}(w)] </tex>. | ||
− | + | Обновлять массив <tex> ancestor </tex> будем следующим образом: | |
− | + | * когда мы приходим в новую вершину <tex>v</tex> мы должны добавить её в новый класс {{---}} <tex>ancestor[v] = v</tex> | |
− | + | * когда просмотрим всё поддерево какого-то ребёнка <tex> u </tex> у вершины <tex> v </tex>, мы должны объединить поддерево ребёнка с классом вершины <tex> v </tex> (<tex>\mathrm{union}(v, u, v)</tex> {{---}} объединить классы вершин <tex> v </tex> и <tex> u </tex>, а наименьшим общим предком представителя нового класса сделать вершину <tex> v </tex>). Система непересекающихся множеств сама определит представителя в зависимости от используемой нами эвристики. Нам надо лишь правильно установить значение массива <tex> ancestor </tex> у нового представителя. | |
После того как мы обработали всех детей вершины <tex>v</tex>, мы можем ответить на все запросы вида <tex>\langle v, u \rangle </tex>, где <tex>u</tex> {{---}} уже посещённая вершина. | После того как мы обработали всех детей вершины <tex>v</tex>, мы можем ответить на все запросы вида <tex>\langle v, u \rangle </tex>, где <tex>u</tex> {{---}} уже посещённая вершина. | ||
Нетрудно заметить, что <tex>lca(v, u) = ancestor[\mathrm{find}(u)]</tex>. Для каждого запроса это условие (что одна вершина уже посещена, а другую мы обрабатываем) выполнится только один раз. | Нетрудно заметить, что <tex>lca(v, u) = ancestor[\mathrm{find}(u)]</tex>. Для каждого запроса это условие (что одна вершина уже посещена, а другую мы обрабатываем) выполнится только один раз. | ||
− | |||
− | |||
=== Реализация === | === Реализация === | ||
Строка 25: | Строка 27: | ||
'''bool''' visited[n] | '''bool''' visited[n] | ||
vector<'''int'''> query[n] | vector<'''int'''> query[n] | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
'''function''' union(x : '''int''', y : '''int''', newAncestor : '''int'''): | '''function''' union(x : '''int''', y : '''int''', newAncestor : '''int'''): | ||
− | + | leader = dsuUnion(x, y) <font color=green> // объединяем классы вершин <tex> x </tex> и <tex> y </tex> и получаем нового представителя класса </font> | |
− | + | ancestor[leader] = newAncestor <font color=green> // устанавливаем нового предка представителю множества </font> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<font color=green>// можно запустить от любой вершины дерева.</font> | <font color=green>// можно запустить от любой вершины дерева.</font> | ||
Строка 52: | Строка 41: | ||
'''for''' i = 0 '''to''' query[v].size - 1 | '''for''' i = 0 '''to''' query[v].size - 1 | ||
'''if''' visited[query[v][i]] | '''if''' visited[query[v][i]] | ||
− | запомнить, что ответ для запроса <tex>\langle v, u \rangle </tex> = ancestor[ | + | запомнить, что ответ для запроса <tex>\langle v, u \rangle </tex> = ancestor[find[q[v][i]]] |
== Корректность == | == Корректность == |
Версия 17:24, 9 июня 2014
Дано дерево и набор запросов: пары вершин
, и для каждой пары нужно найти наименьшего общего предка. Считаем, что все запросы известны заранее, поэтому будем решать задачу оффлайн. Алгоритм позволяет найти ответы для дерева из вершин и запросов за время , то есть при достаточно большом , за на запрос.Алгоритм
Подвесим наше дерево за любую вершину, и запустим обход в глубину из неё. Ответ на каждый запрос мы найдём в течение поиска в глубину. Ответ для вершин и находится, когда мы уже посетили вершину , а так же посетили всех сыновей вершины , и собираемся выйти из неё.
Зафиксируем момент: мы собираемся выйти из вершины
(обработали всех сыновей) и хотим узнать ответ для пары , . Тогда заметим, что ответ — это либо вершина , либо какой-то её предок. Значит, нам нужно найти предка вершины , который является предком вершины с наибольшей глубиной. Заметим, что при фиксированном каждый из предков вершины порождает некоторый класс вершин , для которых он является ответом, в этом классе содержатся все вершины которые находятся "слева" от этого предка.На рисунке разные цвета — разные классы, а белые вершины ещё не просмотренные в
.Классы этих вершин не пересекаются, а значит мы можем их эффективно обрабатывать с помощью системы непересекающихся множеств, которую будем хранить в массиве .
Будем поддерживать массив
, где — наименьший общий предок всех вершин, которые лежат в том же классе, что и . Обновление массива для каждого элемента будет неэффективно. Поэтому зафиксируем в каждом классе какого-то представителя этого класса. Функция вернёт представителя класса, в котором находится вершина . Тогда наименьшим общим предком всех вершин из класса будет вершина .Обновлять массив
будем следующим образом:- когда мы приходим в новую вершину мы должны добавить её в новый класс —
- когда просмотрим всё поддерево какого-то ребёнка у вершины , мы должны объединить поддерево ребёнка с классом вершины ( — объединить классы вершин и , а наименьшим общим предком представителя нового класса сделать вершину ). Система непересекающихся множеств сама определит представителя в зависимости от используемой нами эвристики. Нам надо лишь правильно установить значение массива у нового представителя.
После того как мы обработали всех детей вершины
, мы можем ответить на все запросы вида , где — уже посещённая вершина. Нетрудно заметить, что . Для каждого запроса это условие (что одна вершина уже посещена, а другую мы обрабатываем) выполнится только один раз.Реализация
bool visited[n] vector<int> query[n] function union(x : int, y : int, newAncestor : int): leader = dsuUnion(x, y) // объединяем классы вершини и получаем нового представителя класса ancestor[leader] = newAncestor // устанавливаем нового предка представителю множества // можно запустить от любой вершины дерева. function dfs(v : int): visited[v] = true foreach u : (v, u) in G if not visited[u] dfs(u) union(v, u, v) for i = 0 to query[v].size - 1 if visited[query[v][i]] запомнить, что ответ для запроса = ancestor[find[q[v][i]]]
Корректность
Случай, когда
является наименьшим общим предком вершин и отработает правильно, потому что по алгоритму в этот момент .Предположим, что нашли предка, который не является наименьшим, тогда это нас моментально приводит к противоречию, потому что запросмы должны были рассмотреть ранее — на минимальном предке. Если он не минимальный, значит, есть на какой-то большей глубине, то есть такая вершина, которая была посещена раньше и для которой условия на
и выполнялись, значит, тогда должна была найтись эта вершина в качестве .Оценка сложности
Она состоит из нескольких оценок.
- Обход в глубину выполняет за .
- Операции по объединению множеств, которые в сумме для всех разумных затрачивают операций. Каждый запрос будет рассмотрен дважды — при посещении вершины и , но обработан лишь один раз, поэтому можно считать, что все запросы обработаются суммарно за .
- Для каждого запроса проверка условия и определение результата, опять же, для всех разумных выполняется за .
Итоговая асимптотика получается
, но при достаточно больших ответ за на один запрос.