Level Ancestor problem — различия между версиями
(→Сравнение с наивными реализациями) (Метки: правка с мобильного устройства, правка из мобильной версии) |
|||
Строка 1: | Строка 1: | ||
− | '''Задача о уровне предка''' (англ. "Level Ancestor problem") является задачей о превращении данного | + | '''Задача о уровне предка''' (англ. "Level Ancestor problem") является задачей о превращении данного подвешенного дерева <tex>T</tex> в структуру данных, которая сможет определить предка любого узла на заданном расстоянии от корня дерева. |
{{Задача | {{Задача | ||
− | |definition = Дано | + | |definition = Дано подвешенное дерево <tex>T</tex> c <tex>n</tex> вершинами. Поступают запросы вида <tex>LA(v, k)</tex>, для каждого из которых необходимо |
найти предка вершины <tex>v</tex>, который находится на расстоянии <tex>k</tex> от корня дерева <tex>T</tex>. | найти предка вершины <tex>v</tex>, который находится на расстоянии <tex>k</tex> от корня дерева <tex>T</tex>. | ||
}} | }} | ||
Строка 9: | Строка 9: | ||
Этот алгоритм базируется на различных способах [[Heavy-light декомпозиция | декомпозиции дерева]] (выберем heavy-light декомпозицию), из свойств этого разбиения следует, | Этот алгоритм базируется на различных способах [[Heavy-light декомпозиция | декомпозиции дерева]] (выберем heavy-light декомпозицию), из свойств этого разбиения следует, | ||
что подняться на любую высоту из вершины <tex>v</tex> мы можем за время <tex>O(\log n)</tex>. | что подняться на любую высоту из вершины <tex>v</tex> мы можем за время <tex>O(\log n)</tex>. | ||
− | Данное разбиение можно строить за <tex>O(n)</tex>, что дает нам алгоритм за | + | Данное разбиение можно строить за <tex>O(n)</tex>, что дает нам алгоритм за <tex>\langle O(n), O(\log n) \rangle</tex>. |
В данном примере поступает запрос LA(v,2), на который алгоритм должен дать ответ h. | В данном примере поступает запрос LA(v,2), на который алгоритм должен дать ответ h. | ||
Строка 43: | Строка 43: | ||
== The Macro-Micro-Tree Algorithm == | == The Macro-Micro-Tree Algorithm == | ||
В данном разделе мы докажем, что предподсчет предыдущего алгоритма можно улучшить до <tex>O(n)</tex>. | В данном разделе мы докажем, что предподсчет предыдущего алгоритма можно улучшить до <tex>O(n)</tex>. | ||
− | Для начала рассмотрим алгоритм | + | Для начала рассмотрим алгоритм <tex>\langle O(L\log n + n), O(1)</tex> >, где <tex>L</tex> это количество листьев. |
*С помощью обхода в глубину запомним по одному листу в ее поддереве для каждой вершины | *С помощью обхода в глубину запомним по одному листу в ее поддереве для каждой вершины | ||
*Воспользуемся алгоритмом лестниц, но будем выполнять предподсчет только для листьев. | *Воспользуемся алгоритмом лестниц, но будем выполнять предподсчет только для листьев. | ||
Строка 49: | Строка 49: | ||
*Зададим некую функцию <tex>S(n) = \dfrac{1}{4} \log n</tex> | *Зададим некую функцию <tex>S(n) = \dfrac{1}{4} \log n</tex> | ||
*Посчитаем размер поддерева для каждой вершины с помощью обхода в глубину, после чего удалим все вершины размер поддерева которых меньше чем <tex>S(n)</tex>. | *Посчитаем размер поддерева для каждой вершины с помощью обхода в глубину, после чего удалим все вершины размер поддерева которых меньше чем <tex>S(n)</tex>. | ||
− | *Забудем на время про удаленные поддеревья, для оставшегося дерева наш алгоритм работает за | + | *Забудем на время про удаленные поддеревья, для оставшегося дерева наш алгоритм работает за <tex>\langle O(\dfrac{n}{S(n)} \log n + n), O(1)\rangle </tex>. Получаем алгоритм <tex>\langle O(n), O(1) \rangle </tex>. Для удаленных поддеревьев же выполним полный предподсчет: таких деревьев не более чем <tex>2^{2S(n)}</tex>, что дает асимптотику предподсчета <tex>O(\sqrt{n} \log^2{n}) = o(n) = O(n)</tex>. |
− | В итоге полученный алгоритм действительно работает за | + | В итоге полученный алгоритм действительно работает за <tex>\langle O(n), O(1)\rangle </tex> времени и за <tex>O(n)</tex> памяти. |
== Сравнение с наивными реализациями == | == Сравнение с наивными реализациями == | ||
Используя DFS посчитаем глубину каждой вершины дерева (это можно сделать за <tex>O(n)</tex>), после чего можем из вершины <tex>v</tex> подняться до необходимой глубины вершины <tex>k</tex>, | Используя DFS посчитаем глубину каждой вершины дерева (это можно сделать за <tex>O(n)</tex>), после чего можем из вершины <tex>v</tex> подняться до необходимой глубины вершины <tex>k</tex>, | ||
что так же в худшем случае работает за <tex>O(n)</tex>. | что так же в худшем случае работает за <tex>O(n)</tex>. | ||
− | Получили алгоритм за | + | Получили алгоритм за <tex>\langle O(n), O(n) \rangle</tex> времени и <tex>O(n)</tex> памяти, где время ответа на |
запрос можно улучшить до <tex>O(\log n)</tex> c помощью [[Метод двоичного подъёма | предподсчета двоичных подъемов]] , | запрос можно улучшить до <tex>O(\log n)</tex> c помощью [[Метод двоичного подъёма | предподсчета двоичных подъемов]] , | ||
− | но тогда и время предподсчета в наивной реализации (посчитать подъемы для всех вершин) ухудшится до | + | но тогда и время предподсчета в наивной реализации (посчитать подъемы для всех вершин) ухудшится до <tex>\langle O(n \log n), |
− | O(\log n)</tex | + | O(\log n)\rangle </tex> времени и <tex>O(n \log n)</tex> памяти. Также альтернативой данным двум алгоритмам является полный предподсчет всех возможных запросов, что соответственно дает нам асимптотику < <tex>O(n^2), O(1)</tex> > времени и <tex>O(n^2)</tex> памяти. |
Таким образом, самым оптимальным из описанных как по времени, так и по памяти является алгоритм Macro-Micro-Tree. | Таким образом, самым оптимальным из описанных как по времени, так и по памяти является алгоритм Macro-Micro-Tree. |
Версия 16:55, 18 мая 2019
Задача о уровне предка (англ. "Level Ancestor problem") является задачей о превращении данного подвешенного дерева
в структуру данных, которая сможет определить предка любого узла на заданном расстоянии от корня дерева.
Задача: |
Дано подвешенное дерево | c вершинами. Поступают запросы вида , для каждого из которых необходимо найти предка вершины , который находится на расстоянии от корня дерева .
Содержание
Использование Heavy-light декомпозиции
Этот алгоритм базируется на различных способах декомпозиции дерева (выберем heavy-light декомпозицию), из свойств этого разбиения следует, что подняться на любую высоту из вершины мы можем за время . Данное разбиение можно строить за , что дает нам алгоритм за .
В данном примере поступает запрос LA(v,2), на который алгоритм должен дать ответ h.
Алгоритм лестниц
Longest path decomposition
Разобьем все вершины на пути следующим образом. Обойдем дерево с помощью обхода в глубину, пусть мы стоим в вершине
, обойдем всех ее детей, добавив в путь, идущий в самое глубокое поддерево, т.е. в котором находится вершина с самой большой глубиной. Для каждой вершины сохраним номер пути в который она входит.Ladder decomposition
Увеличим каждый путь в два раза вверх, для каждого нового пути сохраним все входящие в него вершины, а для каждой вершины сохраним ее номер в пути, в который она входит. Построение обычной longest-path декомпозиции займет у нас
времени (обход в глубину), соответственно удлиннение каждого пути ухудшит асимптотику до .После этого посчитаем двоичные подъемы для каждой вершины за
, что соответственно не ухудшит асимптотику.Псевдокод
Пусть после этого нам пришел запрос
.function LA(int v,int k): int n = h(v); // получаем глубину вершиныn = n - k; // на столько необходимо подняться до ответа i = ; v = p_i[v] // делаем максимально большой прыжок вверх i = n - i; // на столько осталось еще подняться return way[num_on_way[v] - i]; // так как теперь и ответ находятся на одном пути
Доказательство корректности
Рассмотрим путь, на котором лежит вершина
до удвоения. Он длины хотя бы , так как мы точно знаем, что существует вершина потомок , расстояние до которого ровно (это вершина, из которой мы только что пришли). Значит, после удвоения этот путь стал длины хотя бы , причем хотя бы вершин в нем - предки . Это означает, что вершина, которую мы ищем, находится на этом пути (иначе бы мы могли до этого прыгнуть еще на вверх). Так как мы знаем позицию в этом пути, то нужную вершину мы можем найти за .Таким образом, наш алгоритм работает за <
> времени и за памяти. Методом четырех русских данный метод можно улучшить до < > с помощью оптимизации предподсчета.The Macro-Micro-Tree Algorithm
В данном разделе мы докажем, что предподсчет предыдущего алгоритма можно улучшить до
. Для начала рассмотрим алгоритм >, где это количество листьев.- С помощью обхода в глубину запомним по одному листу в ее поддереве для каждой вершины
- Воспользуемся алгоритмом лестниц, но будем выполнять предподсчет только для листьев.
Рассмотрим как можно улучшить данный алгоритм:
- Зададим некую функцию
- Посчитаем размер поддерева для каждой вершины с помощью обхода в глубину, после чего удалим все вершины размер поддерева которых меньше чем .
- Забудем на время про удаленные поддеревья, для оставшегося дерева наш алгоритм работает за . Получаем алгоритм . Для удаленных поддеревьев же выполним полный предподсчет: таких деревьев не более чем , что дает асимптотику предподсчета .
В итоге полученный алгоритм действительно работает за
времени и за памяти.Сравнение с наивными реализациями
Используя DFS посчитаем глубину каждой вершины дерева (это можно сделать за предподсчета двоичных подъемов , но тогда и время предподсчета в наивной реализации (посчитать подъемы для всех вершин) ухудшится до времени и памяти. Также альтернативой данным двум алгоритмам является полный предподсчет всех возможных запросов, что соответственно дает нам асимптотику < > времени и памяти.
), после чего можем из вершины подняться до необходимой глубины вершины , что так же в худшем случае работает за . Получили алгоритм за времени и памяти, где время ответа на запрос можно улучшить до c помощьюТаким образом, самым оптимальным из описанных как по времени, так и по памяти является алгоритм Macro-Micro-Tree.