Level Ancestor problem — различия между версиями
Строка 38: | Строка 38: | ||
Пусть после этого нам пришел запрос <tex>LA(v, k)</tex>. | Пусть после этого нам пришел запрос <tex>LA(v, k)</tex>. | ||
− | + | <code> | |
− | + | '''function'''LA(int v,int k): | |
− | + | ''int n'' = h(v); //получаем | |
+ | n = n - k; | ||
+ | i = <tex>\log n</tex>; | ||
+ | v = p_i[v] | ||
+ | i = n - i; | ||
+ | ''return'' way[num_on_way[v] - i]; //так как теперь <tex>v</tex> | ||
+ | и ответ находятся на одном пути | ||
+ | </code> | ||
+ | |||
=== Доказательство корректности === | === Доказательство корректности === | ||
Рассмотрим путь, на котором лежит вершина <tex>v</tex> до удвоения. Он длины хотя бы <tex>2^i</tex>, так как мы точно знаем, что существует вершина потомок <tex>v</tex>, расстояние до которого ровно <tex>2^i</tex> (это вершина, из которой мы только что пришли). Значит, после удвоения этот путь стал длины хотя бы <tex>2^{i + 1}</tex>, причем хотя бы <tex>2^i</tex> вершин в нем - предки <tex>v</tex>. Это означает, что вершина, которую мы ищем, находится на этом пути (иначе бы мы могли до этого прыгнуть еще на <tex>2^i</tex> вверх). Так как мы знаем позицию <tex>v</tex> в этом пути, то нужную вершину мы можем найти за <tex>O(1)</tex>. | Рассмотрим путь, на котором лежит вершина <tex>v</tex> до удвоения. Он длины хотя бы <tex>2^i</tex>, так как мы точно знаем, что существует вершина потомок <tex>v</tex>, расстояние до которого ровно <tex>2^i</tex> (это вершина, из которой мы только что пришли). Значит, после удвоения этот путь стал длины хотя бы <tex>2^{i + 1}</tex>, причем хотя бы <tex>2^i</tex> вершин в нем - предки <tex>v</tex>. Это означает, что вершина, которую мы ищем, находится на этом пути (иначе бы мы могли до этого прыгнуть еще на <tex>2^i</tex> вверх). Так как мы знаем позицию <tex>v</tex> в этом пути, то нужную вершину мы можем найти за <tex>O(1)</tex>. |
Версия 16:52, 15 мая 2019
Задача о уровне предка (англ. "Level Ancestor problem") является задачей о превращении данного корневого подвешенного дерева
в структуру данных, которая сможет определить предка любого узла на заданном расстоянии от корня дерева.
Задача: |
Дано корневое подвешенное дерево | c вершинами. Поступают запросы вида , для каждого из которых необходимо найти предка вершины , который находится на расстоянии от корня дерева .
Содержание
Наивная реализация и двоичные подъемы
Используя обход в глубину посчитаем глубину каждой вершины дерева (это можно сделать за предподсчета двоичных подъемов , но тогда и время предподсчета в наивной реализации (посчитать подъемы для всех вершин) ухудшится до < >. Альтернативой данным двум алгоритмам является полный предподсчет всех возможных запросов, что соответственно дает нам асимптотику < >.
), после чего можем из вершины подняться до необходимой глубины вершины , что так же в худшем случае работает за . Получили алгоритм за < >, где время ответа на запрос можно улучшить до c помощьюВ данном примере поступает запрос
, на который алгоритм должен дать ответ .Использование Heavy-light декомпозиции
Этот алгоритм базируется на различных способах декомпозиции дерева (выберем heavy-light декомпозицию), из свойств этого разбиения следует, что подняться на любую высоту из вершины мы можем за время . Данное разбиение можно строить за , что дает нам алгоритм за < >.
Алгоритм лестниц
Longest path decomposition
Разобьем все вершины на пути следующим образом. Обойдем дерево с помощью обхода в глубину, пусть мы стоим в вершине
, обойдем всех ее детей, добавив в путь, идущий в самое глубокое поддерево, т.е. в котором находится вершина с самой большой глубиной. Для каждой вершины сохраним номер пути в который она входит.Ladder decomposition
Увеличим каждый путь в два раза вверх, для каждого нового пути сохраним все входящие в него вершины, а для каждой вершины сохраним ее номер в пути, в который она входит. Построение обычной longest-path декомпозиции займет у нас
времени (обход в глубину), соответственно удлиннение каждого пути ухудшит асимптотику до .После этого посчитаем двоичные подъемы для каждой вершины за
, что соответственно не ухудшит асимптотику.Псевдокод
Пусть после этого нам пришел запрос
.
functionLA(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
В данном разделе мы докажем, что предподсчет предыдущего алгоритма можно улучшить до
. Для начала рассмотрим алгоритм < >, где это количество листьев.- С помощью обхода в глубину запомним по одному листу в ее поддереве для каждой вершины
- Воспользуемся алгоритмом лестниц, но будем выполнять предподсчет только для листьев.
Рассмотрим как можно улучшить данный алгоритм:
- Зададим некую функцию
- Посчитаем размер поддерева для каждой вершины с помощью обхода в глубину, после чего удалим все вершины размер поддерева которых меньше чем .
- Забудем на время про удаленные поддеревья, для оставшегося дерева наш алгоритм работает за < >. Получаем алгоритм < >. Для удаленных поддеревьев же выполним полный предподсчет: таких деревьев не более чем , что дает асимптотику предподсчета .
В итоге полученный алгоритм действительно работает за <
> времени и за памяти.