Level Ancestor problem — различия между версиями
Romech (обсуждение | вклад)  (→Сравнение с наивными реализациями)  | 
				Romech (обсуждение | вклад)   (→Псевдокод)  | 
				||
| Строка 29: | Строка 29: | ||
Пусть после этого нам пришел запрос <tex>LA(v, k)</tex>.  | Пусть после этого нам пришел запрос <tex>LA(v, k)</tex>.  | ||
| + | |||
| + | *<tex>p[i][v]</tex> - i-тый двоичный подъем в предка вершины <tex>v</tex>  | ||
| + | *way[v] - путь, проходящий через данную вершину  | ||
| + | *num[v] - номер данной вершины на пути  | ||
| + | *ladder[path][i] - возвращает i-тую вершину на пути path  | ||
| + | |||
    '''function''' LA('''int''' v,'''int''' k):  |     '''function''' LA('''int''' v,'''int''' k):  | ||
       '''int''' n = h(v); ''<font color="green">// получаем глубину вершины <tex>v</tex></font>''  |        '''int''' n = h(v); ''<font color="green">// получаем глубину вершины <tex>v</tex></font>''  | ||
       n = n - k;  ''<font color="green">// на столько необходимо подняться до ответа</font>''  |        n = n - k;  ''<font color="green">// на столько необходимо подняться до ответа</font>''  | ||
       i = <tex>\log_2 n</tex>;     |        i = <tex>\log_2 n</tex>;     | ||
| − |        v =   | + |        v = p[i][v]  ''<font color="green">// делаем максимально большой прыжок вверх</font>''  | 
       i = n - i;  ''<font color="green">// на столько осталось еще подняться</font>''  |        i = n - i;  ''<font color="green">// на столько осталось еще подняться</font>''  | ||
| − |        '''return''' way[  | + |        '''return''' ladder[way[v]][num[v] - i]; ''<font color="green">// так как теперь <tex>v</tex> и ответ находятся на одном пути</font>''  | 
=== Доказательство корректности ===  | === Доказательство корректности ===  | ||
Версия 18:27, 18 мая 2019
Задача о уровне предка (англ. "Level Ancestor problem") является задачей о превращении данного подвешенного дерева в структуру данных, которая сможет определить предка любого узла на заданном расстоянии от корня дерева.
| Задача: | 
| Дано подвешенное дерево c вершинами. Поступают запросы вида , для каждого из которых необходимо найти предка вершины , который находится на расстоянии от корня дерева . | 
Содержание
Использование Heavy-light декомпозиции
Этот алгоритм базируется на различных способах декомпозиции дерева (выберем heavy-light декомпозицию), из свойств этого разбиения следует, что подняться на любую высоту из вершины мы можем за время . Данное разбиение можно строить за , что дает нам алгоритм за .
В данном примере поступает запрос LA(v,2), на который алгоритм должен дать ответ h.
Алгоритм лестниц
Longest path decomposition
Разобьем все вершины на пути следующим образом. Обойдем дерево с помощью обхода в глубину, пусть мы стоим в вершине , обойдем всех ее детей, добавив в путь, идущий в самое глубокое поддерево, т.е. в котором находится вершина с самой большой глубиной. Для каждой вершины сохраним номер пути в который она входит.
Ladder decomposition
Увеличим каждый путь в два раза вверх, для каждого нового пути сохраним все входящие в него вершины, а для каждой вершины сохраним ее номер в пути, в который она входит. Построение обычной longest-path декомпозиции займет у нас времени (обход в глубину), соответственно удлиннение каждого пути ухудшит асимптотику до .
После этого посчитаем двоичные подъемы для каждой вершины за , что соответственно не ухудшит асимптотику.
Псевдокод
Пусть после этого нам пришел запрос .
- - i-тый двоичный подъем в предка вершины
 - way[v] - путь, проходящий через данную вершину
 - num[v] - номер данной вершины на пути
 - ladder[path][i] - возвращает i-тую вершину на пути path
 
  function LA(int v,int k):
     int n = h(v); // получаем глубину вершины 
     n = n - k;  // на столько необходимо подняться до ответа
     i = ;  
     v = p[i][v]  // делаем максимально большой прыжок вверх
     i = n - i;  // на столько осталось еще подняться
     return ladder[way[v]][num[v] - i]; // так как теперь  и ответ находятся на одном пути
Доказательство корректности
Рассмотрим путь, на котором лежит вершина до удвоения. Он длины хотя бы , так как мы точно знаем, что существует вершина потомок , расстояние до которого ровно (это вершина, из которой мы только что пришли). Значит, после удвоения этот путь стал длины хотя бы , причем хотя бы вершин в нем - предки . Это означает, что вершина, которую мы ищем, находится на этом пути (иначе бы мы могли до этого прыгнуть еще на вверх). Так как мы знаем позицию в этом пути, то нужную вершину мы можем найти за .
Таким образом, наш алгоритм работает за времени и за памяти. Методом четырех русских данный метод можно улучшить до с помощью оптимизации предподсчета.
The Macro-Micro-Tree Algorithm
В данном разделе мы докажем, что предподсчет предыдущего алгоритма можно улучшить до . Для начала рассмотрим алгоритм >, где это количество листьев.
- С помощью обхода в глубину запомним по одному листу в ее поддереве для каждой вершины
 - Воспользуемся алгоритмом лестниц, но будем выполнять предподсчет только для листьев.
 
Рассмотрим как можно улучшить данный алгоритм:
- Зададим некую функцию
 - Посчитаем размер поддерева для каждой вершины с помощью обхода в глубину, после чего удалим все вершины размер поддерева которых меньше чем .
 - Забудем на время про удаленные поддеревья, для оставшегося дерева наш алгоритм работает за . Получаем алгоритм . Для удаленных поддеревьев же выполним полный предподсчет: таких деревьев не более чем , что дает асимптотику предподсчета .
 
В итоге полученный алгоритм действительно работает за времени и за памяти.
Сравнение с наивными реализациями
Используя DFS посчитаем глубину каждой вершины дерева (это можно сделать за ), после чего можем из вершины подняться до необходимой глубины вершины , что так же в худшем случае работает за . Получили алгоритм за времени и памяти, где время ответа на запрос можно улучшить до c помощью предподсчета двоичных подъемов , но тогда и время предподсчета в наивной реализации (посчитать подъемы для всех вершин) ухудшится до времени и памяти. Также альтернативой данным двум алгоритмам является полный предподсчет всех возможных запросов, что соответственно дает нам асимптотику времени и памяти.
Сравнение различных асимптотик из данной статьи:
| Обычный подъем до нужного уровня | |||
|---|---|---|---|
| Полный предподсчет | |||
| Двоичные подъемы | |||
| Декомпозиция | |||
| Алгоритм лестниц | |||
| Macro-Micro-Tree Algorithm |