Алгоритм Тарьяна поиска LCA за O(1) в оффлайн — различия между версиями
Shersh (обсуждение | вклад) (→Алгоритм) |
Shersh (обсуждение | вклад) (→Реализация) |
||
| Строка 26: | Строка 26: | ||
'''bool''' visited[n] | '''bool''' visited[n] | ||
| − | |||
'''function''' union(x : '''int''', y : '''int''', newAncestor : '''int'''): | '''function''' union(x : '''int''', y : '''int''', newAncestor : '''int'''): | ||
| Строка 39: | Строка 38: | ||
dfs(u) | dfs(u) | ||
union(v, u, v) | union(v, u, v) | ||
| − | ''' | + | '''foreach''' u : <tex>\langle v, u \rangle </tex> {{---}} есть такой запрос |
| − | '''if''' visited[ | + | '''if''' visited[u] |
| − | запомнить, что ответ для запроса <tex>\langle v, u \rangle </tex> = ancestor[find[ | + | запомнить, что ответ для запроса <tex>\langle v, u \rangle </tex> = ancestor[find[u]] |
== Корректность == | == Корректность == | ||
Версия 17:26, 9 июня 2014
Дано дерево и набор запросов: пары вершин , и для каждой пары нужно найти наименьшего общего предка. Считаем, что все запросы известны заранее, поэтому будем решать задачу оффлайн. Алгоритм позволяет найти ответы для дерева из вершин и запросов за время , то есть при достаточно большом , за на запрос.
Алгоритм
Подвесим наше дерево за любую вершину, и запустим обход в глубину из неё. Ответ на каждый запрос мы найдём в течение поиска в глубину. Ответ для вершин и находится, когда мы уже посетили вершину , а так же посетили всех сыновей вершины , и собираемся выйти из неё.
Зафиксируем момент: мы собираемся выйти из вершины (обработали всех сыновей) и хотим узнать ответ для пары , . Тогда заметим, что ответ — это либо вершина , либо какой-то её предок. Значит, нам нужно найти предка вершины , который является предком вершины с наибольшей глубиной. Заметим, что при фиксированном каждый из предков вершины порождает некоторый класс вершин , для которых он является ответом, в этом классе содержатся все вершины которые находятся "слева" от этого предка.
На рисунке разные цвета — разные классы, а белые вершины ещё не просмотренные в .
Классы этих вершин не пересекаются, а значит мы можем их эффективно обрабатывать с помощью системы непересекающихся множеств, которую будем хранить в массиве .
Будем поддерживать массив , где — наименьший общий предок всех вершин, которые лежат в том же классе, что и . Обновление массива для каждого элемента будет неэффективно. Поэтому зафиксируем в каждом классе какого-то представителя этого класса. Функция вернёт представителя класса, в котором находится вершина . Тогда наименьшим общим предком всех вершин из класса будет вершина .
Обновлять массив будем следующим образом:
- когда мы приходим в новую вершину мы должны добавить её в новый класс —
- когда просмотрим всё поддерево какого-то ребёнка у вершины , мы должны объединить поддерево ребёнка с классом вершины ( — объединить классы вершин и , а наименьшим общим предком представителя нового класса сделать вершину ). Система непересекающихся множеств сама определит представителя в зависимости от используемой нами эвристики. Нам надо лишь правильно установить значение массива у нового представителя.
После того как мы обработали всех детей вершины , мы можем ответить на все запросы вида , где — уже посещённая вершина. Нетрудно заметить, что . Для каждого запроса это условие (что одна вершина уже посещена, а другую мы обрабатываем) выполнится только один раз.
Реализация
bool visited[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)
foreach u : — есть такой запрос
if visited[u]
запомнить, что ответ для запроса = ancestor[find[u]]
Корректность
Случай, когда является наименьшим общим предком вершин и отработает правильно, потому что по алгоритму в этот момент .
Предположим, что нашли предка, который не является наименьшим, тогда это нас моментально приводит к противоречию, потому что запросмы должны были рассмотреть ранее — на минимальном предке. Если он не минимальный, значит, есть на какой-то большей глубине, то есть такая вершина, которая была посещена раньше и для которой условия на и выполнялись, значит, тогда должна была найтись эта вершина в качестве .
Оценка сложности
Она состоит из нескольких оценок.
- Обход в глубину выполняет за .
- Операции по объединению множеств, которые в сумме для всех разумных затрачивают операций. Каждый запрос будет рассмотрен дважды — при посещении вершины и , но обработан лишь один раз, поэтому можно считать, что все запросы обработаются суммарно за .
- Для каждого запроса проверка условия и определение результата, опять же, для всех разумных выполняется за .
Итоговая асимптотика получается , но при достаточно больших ответ за на один запрос.
