В этой статье мы рассмотрим различные методы сведения задач на деревьях к задачам на отрезках, которые можно решать уже известными структурами данных.
Перед прочтением рекомендуется вспомнить свойства массивов и , получаемых обходом в глубину.
#Запросы на поддеревьях
Первым важным свойством dfs
является то, что -ы вершин любого поддеререва являются каким-то непрерывным отрезком.
Это свойство можно использовать для обработки разных запросов на поддеревьях, сводя их к запросам на подотрезках, которые уже можно решать стандартными методами — например, через дерево отрезков.
#Сумма на поддереве
Задача. Дано корневое дерево. Рядом с каждой вершиной записано число. Поступают два типа запросов: изменить какое-то из значений и найти сумму значений на поддереве вершины .
Выпишем все числа у вершин в один массив в позиции, соответствующие их -ам. Что такое «сумма на поддереве » в терминах этого массива? Это просто сумма на подотрезке .
Построим поверх массива дерево отрезков или любую другую структуру для динамической суммы, и для запросов первого типа будем отправлять структуре запрос изменения ячейки , а для второго будем делать запрос суммы на подотрезке .
#Запросы на уровнях
Помимо и бывает полезно во время обхода считать глубину вершины. Будем в дальнейшем обозначать её за .
#Level Ancestor
Задача. Дано корневое дерево. Требуется отвечать на запросы нахождения -того предка вершины , то есть вершины-предка, находящейся на расстоянии от .
Создадим векторов, где — высота дерева. Для каждой вершины, во время прохода в dfs, добавим её в вектор, соответствующей её глубине. Получаем отсортированных векторов.
Теперь заметим, что внутри одного вектора все отрезки поддеревьев вершин — — тоже не пересекаются, а значит ещё и отсортированы. Тогда для ответа на запрос мы можем просто взять вершины-запроса, посмотреть на вектор нужного уровня и за сделать бинпоиск по нужному отрезку.
Также существует другой способ, требующий времени на запрос, но памяти на предподсчет — лестничная декомпозиция.
#Поддеревья заданной глубины
Задача. Дано корневое дерево. Рядом с каждой вершиной записано число. Поступают два типа запросов: изменить какое-то из значений и найти сумму значений на поддереве вершины среди вершин на расстоянии не более от неё.
Когда используется одновременно и глубина, и структура дерева, обычно помогает взглянуть на задачу геометрически. Сопоставим каждой вершине точку . Тогда как выглядят искомые множества вершин? Они соответствуют тем точкам, у которых первая координата лежит между и , а вторая больше .
Соответственно, запросы на таких поддеревьях можно свести к запросам на прямоугольниках, которые можно решать либо «в лоб» двумерными структурами, либо применить методы вроде сканирующей прямой.
#Запросы на путях
Если даны запросы на путях в оффлайн, то почти всегда их можно решить каким-то предподсчетом.
#Сумма на пути
Задача. Дано дерево. У каждого ребра есть какое-то число. Нужно отвечать на запросы нахождения суммы на пути.
Предподсчитаем во время обхода в глубину массив , где это сумма чисел на пути от корня до вершины .
Любой путь в дереве разбивается на один или два вертикальных пути. Найдем наибольшего общего предка и разобьём сумму как .
#Xor на пути
Задача. Дано дерево. У каждого ребра есть какое-то число. Нужно отвечать на запросы нахождения xor
-суммы на пути.
Заметим, что в случае с xor
-суммой не нужно даже искать эти пути — по модулю двойки слагаемое «отменит» само себя. Достаточно просто вывести s[a] ^ s[b]
.
#Число различных чисел на пути
Задача. Дано дерево. У каждого ребра есть какое-то число. Требуется отвечать на запросов нахождения числа различных значений на пути с по .
Для более сложных запросов в оффлайн почти всегда нужно использовать обобщение алгоритма Мо на деревья, заключающееся примерно в следующем.
- Выпишем эйлеров обход дерева: при каждом проходе по ребру выписываем номер ребра (номер нижней/верхней вершины). Заметим, что каждое ребро будет выписано дважды: при входе и выходе из нижней вершины.
- Получив запрос, определим «более раннюю» вершину (с меньшим ) и рассмотрим подотрезок эйлерова обхода с по .
- Какая-то подпоследовательность ребер на этом отрезке является кратчайшим путем между и . А именно, это будут ровно те ребра, которые встречались на этом отрезке ровно один раз — все, которые встречались дважды, ведут в какие-то побочные поддеревья.
Теперь рядом с номерами ребер в обходе выпишем ещё и соответствующие им числа. Тогда запрос нахождения числа различных значений на пути эквивалентен нахождению числа различных значений у ребер, встречающихся ровно один раз на подотрезке. Эту задачу уже можно решить обычным алгоритмом Мо.