Heavy-light декомпозиция — различия между версиями
Строка 2: | Строка 2: | ||
==Описание задачи== | ==Описание задачи== | ||
{{Задача | {{Задача | ||
− | |definition=Имеется дерево <tex>T</tex> c <tex>n</tex> вершинами и необходимо проводить операции на нем на пути от вершины <tex>v</tex> до вершины <tex>u</tex>.}} | + | |definition=Имеется подвешенное дерево <tex>T</tex> c <tex>n</tex> вершинами и необходимо проводить операции на нем на пути от вершины <tex>v</tex> до вершины <tex>u</tex>.}} |
Примеры запросов: | Примеры запросов: | ||
<ul> | <ul> | ||
− | <li> | + | <li>сумма на пути,</li> |
− | <li> | + | <li>максимум на пути,</li> |
− | <li> | + | <li>количество рёбер на пути, вес которых больше заданного <tex>c</tex>.</li> |
</ul> | </ul> | ||
Примеры модификаций: | Примеры модификаций: | ||
<ul> | <ul> | ||
− | <li> | + | <li>модификация одного ребра,</li> |
− | <li> | + | <li>прибавление к весу всех рёбер на пути,</li> |
− | <li> | + | <li>установка веса всех рёбер на пути в заданное <tex>c</tex>.</li> |
</ul> | </ul> | ||
Строка 22: | Строка 22: | ||
Декомпозиция заключается в классификации всех рёбер дерева <tex>T</tex> в 2 вида: легкие и тяжёлые. Введём функцию <tex>s(v)</tex>, которая будет обозначать размер поддерева вершины <tex>v</tex>. | Декомпозиция заключается в классификации всех рёбер дерева <tex>T</tex> в 2 вида: легкие и тяжёлые. Введём функцию <tex>s(v)</tex>, которая будет обозначать размер поддерева вершины <tex>v</tex>. | ||
− | '''Тяжёлые ребра ( | + | '''Тяжёлые ребра''' (англ. ''heavy edge'') {{---}} ребра <tex>(u, v)</tex> такие, что <tex>s(v) \geqslant</tex> <tex dpi="150">\frac{s(u)}{2}</tex>. |
− | '''Лёгкие ребра ( | + | '''Лёгкие ребра''' (англ. ''light edge'') {{---}} соответственно все остальные. |
Очевидно, что из вершины может выходить как максимум одно тяжёлое ребро, т.к. иначе у нас есть два поддерева по как минимум <tex dpi="150">\frac{s(u)}{2}</tex> вершин, а также сама вершина <tex>u</tex>. Итого <tex>s(u) + 1</tex> вершин, тогда как у нас всего <tex>s(u)</tex> вершин в поддереве. | Очевидно, что из вершины может выходить как максимум одно тяжёлое ребро, т.к. иначе у нас есть два поддерева по как минимум <tex dpi="150">\frac{s(u)}{2}</tex> вершин, а также сама вершина <tex>u</tex>. Итого <tex>s(u) + 1</tex> вершин, тогда как у нас всего <tex>s(u)</tex> вершин в поддереве. | ||
Строка 44: | Строка 44: | ||
==Применение== | ==Применение== | ||
===Сумма на пути=== | ===Сумма на пути=== | ||
− | Классическая задача о сумме на пути в дереве с <tex>n</tex> решается с помощью Heavy-light декомпозиции за время. Возможны модификации веса. | + | Классическая задача о сумме на пути в дереве с <tex>n</tex> решается с помощью Heavy-light декомпозиции за время <tex>O(\log^2{n})</tex>. Возможны модификации веса. |
Построим [[Дерево отрезков. Построение|дерево отрезков]] над каждым путём. Рассмотрим запрос <tex>sum(u, v)</tex>. Давайте найдем вершину <tex>c</tex>, которая является <tex>LCA(u, v)</tex> (например с помощью [[Метод двоичного подъема|двоичного подъема]]. Мы разбили запрос на два: <tex>(u, c)</tex> и <tex>(c, v)</tex>, на каждый из которых можно легко ответить выбирая путь, содержащий самую нижнюю вершину и вырезать его, пока этот путь не содержит <tex>c</tex>. | Построим [[Дерево отрезков. Построение|дерево отрезков]] над каждым путём. Рассмотрим запрос <tex>sum(u, v)</tex>. Давайте найдем вершину <tex>c</tex>, которая является <tex>LCA(u, v)</tex> (например с помощью [[Метод двоичного подъема|двоичного подъема]]. Мы разбили запрос на два: <tex>(u, c)</tex> и <tex>(c, v)</tex>, на каждый из которых можно легко ответить выбирая путь, содержащий самую нижнюю вершину и вырезать его, пока этот путь не содержит <tex>c</tex>. | ||
Строка 51: | Строка 51: | ||
Хоть это и не самый эффективный способ для решения этой задачи, но можно заметить, что с помощью такого трюка мы можем решить широкий класс задач просто сведя её к задаче о запросах в дереве отрезков (такие как максимум на пути, покраска на пути, прибавление на пути и др.). | Хоть это и не самый эффективный способ для решения этой задачи, но можно заметить, что с помощью такого трюка мы можем решить широкий класс задач просто сведя её к задаче о запросах в дереве отрезков (такие как максимум на пути, покраска на пути, прибавление на пути и др.). | ||
+ | ===LCA=== | ||
+ | Задача о наименьшем общем предке для двух вершин в дереве с <tex>n</tex> решается с помощью Heavy-light декомпозиции за время <tex>O(\log{n})</tex>. | ||
+ | |||
+ | Мы можем проверять, что вершина является предком другой вершины за <tex>O(1)</tex> с помощью времен входа\выхода в каждую вершину. Тогда рассмотрим самый нижний путь (содержащий первую вершину) и посмотрим на его корень (самая высокая вершина в пути). Если эта вершина является предком второй вершины, то ответ где-то в этом пути, иначе выкинем этот путь и рассмотрим следующий вверх за ним. | ||
+ | |||
+ | Когда мы определили путь, в котором содержится ответ, мы можем с помощью бинпоиска найти в нём первую вершину, являющуюся предком второй вершины. Эта вершина является ответом. | ||
+ | |||
+ | Итого мы рассмотрели корень каждого пути за <tex>O(1)</tex>, а путей всего <tex>O(\log{n})</tex>. И в последнем пути один раз запустили бинпоиск, который отработал за <tex>O(\log{n})</tex>. Итоговая асимптотика <tex>O(\log{n})</tex>. | ||
==Реализация== | ==Реализация== | ||
Опущены некоторые детали реализации: построение и предподсчёт. | Опущены некоторые детали реализации: построение и предподсчёт. | ||
<ul> | <ul> | ||
− | <li><tex>\mathrm{getPath}</tex> {{---}} функция, позволяющая найти номер пути, относящийся к конкретной вершине | + | <li><tex>\mathrm{getPath}</tex> {{---}} функция, позволяющая найти номер пути, относящийся к конкретной вершине,</li> |
− | <li><tex>\mathrm{pathRoot}</tex> {{---}} функция, позволяющая найти корень пути (самую верхнюю вершину) | + | <li><tex>\mathrm{pathRoot}</tex> {{---}} функция, позволяющая найти корень пути (самую верхнюю вершину),</li> |
− | <li><tex>\mathrm{pathPos}</tex> {{---}} функция, позволяющая найти смещение вершины в пути относительно корня пути | + | <li><tex>\mathrm{pathPos}</tex> {{---}} функция, позволяющая найти смещение вершины в пути относительно корня пути,</li> |
<li><tex>\mathrm{getValue(\mathtt{i}, \mathtt{j})}</tex> {{---}} функция, позволяющая найти вес <tex>\mathtt{j}</tex>-ого ребра в <tex>\mathtt{i}</tex>-ом пути.</li> | <li><tex>\mathrm{getValue(\mathtt{i}, \mathtt{j})}</tex> {{---}} функция, позволяющая найти вес <tex>\mathtt{j}</tex>-ого ребра в <tex>\mathtt{i}</tex>-ом пути.</li> | ||
</ul> | </ul> |
Версия 21:41, 8 июня 2015
Heavy-light декомпозиция — техника разбиения подвешенного дерева на множество путей для решения задач о запросах на пути в дереве (в том числе с модификациями).
Содержание
Описание задачи
Задача: |
Имеется подвешенное дерево | c вершинами и необходимо проводить операции на нем на пути от вершины до вершины .
Примеры запросов:
- сумма на пути,
- максимум на пути,
- количество рёбер на пути, вес которых больше заданного .
Примеры модификаций:
- модификация одного ребра,
- прибавление к весу всех рёбер на пути,
- установка веса всех рёбер на пути в заданное .
Множество подобных запросов делаются за время за полином от логарифма (обычно
) с помощью Heavy-light декомпозиции.Описание декомпозиции
Декомпозиция заключается в классификации всех рёбер дерева
в 2 вида: легкие и тяжёлые. Введём функцию , которая будет обозначать размер поддерева вершины .Тяжёлые ребра (англ. heavy edge) — ребра
такие, что .Лёгкие ребра (англ. light edge) — соответственно все остальные.
Очевидно, что из вершины может выходить как максимум одно тяжёлое ребро, т.к. иначе у нас есть два поддерева по как минимум
вершин, а также сама вершина . Итого вершин, тогда как у нас всего вершин в поддереве.Теперь рассмотрим вершины, из которых не ведет вниз ни одно тяжёлое ребро. Будем идти от них вверх до корня или пока не пройдем легкое ребро. Получится какое-то множество путей. Утверждается, что полученная таким путём декомпозиция будет являться искомой и корректной.
Утверждение: |
Полученная декомпозиция является искомой. |
Докажем по отдельности корректность декомпозиции.
|
Существует вариант Heavy-light декомпозиции на вершинно-непересекающихся путях. Чтобы получить такой путь нужно всего-лишь выкинуть последнее ребро из всех путей в рёберно-непересекающейся декомпозиции. Это может быть удобно при решении задач, где веса находятся не на рёбрах, а на вершинах и соответствующие запросы также делаются на вершинах.
Применение
Сумма на пути
Классическая задача о сумме на пути в дереве с
решается с помощью Heavy-light декомпозиции за время . Возможны модификации веса.Построим дерево отрезков над каждым путём. Рассмотрим запрос . Давайте найдем вершину , которая является (например с помощью двоичного подъема. Мы разбили запрос на два: и , на каждый из которых можно легко ответить выбирая путь, содержащий самую нижнюю вершину и вырезать его, пока этот путь не содержит .
Так мы сможем получать ответ на пути за
. А всего таких путей нужно будет рассмотреть . Итого мы способны решить эту задачу за время .Хоть это и не самый эффективный способ для решения этой задачи, но можно заметить, что с помощью такого трюка мы можем решить широкий класс задач просто сведя её к задаче о запросах в дереве отрезков (такие как максимум на пути, покраска на пути, прибавление на пути и др.).
LCA
Задача о наименьшем общем предке для двух вершин в дереве с
решается с помощью Heavy-light декомпозиции за время .Мы можем проверять, что вершина является предком другой вершины за
с помощью времен входа\выхода в каждую вершину. Тогда рассмотрим самый нижний путь (содержащий первую вершину) и посмотрим на его корень (самая высокая вершина в пути). Если эта вершина является предком второй вершины, то ответ где-то в этом пути, иначе выкинем этот путь и рассмотрим следующий вверх за ним.Когда мы определили путь, в котором содержится ответ, мы можем с помощью бинпоиска найти в нём первую вершину, являющуюся предком второй вершины. Эта вершина является ответом.
Итого мы рассмотрели корень каждого пути за
, а путей всего . И в последнем пути один раз запустили бинпоиск, который отработал за . Итоговая асимптотика .Реализация
Опущены некоторые детали реализации: построение и предподсчёт.
- — функция, позволяющая найти номер пути, относящийся к конкретной вершине,
- — функция, позволяющая найти корень пути (самую верхнюю вершину),
- — функция, позволяющая найти смещение вершины в пути относительно корня пути,
- — функция, позволяющая найти вес -ого ребра в -ом пути.
Пример реализации запроса суммы на пути:
int queryPath(int path, int from, int to): int res = 0 while from <= to: if from mod 2 == 1: res += getValue(path, from) if to mod 2 == 0: res += getValue(path, to) to = (to - 1) / 2 from = (from + 1) / 2 return res
int query(int u, int v): int res = 0 int root = pathRoot(u) while not (root is ancestor of v) // поднимаемся до тех пор, пока наш путь не содержит общего предка u и v res += queryPath(getPath(u), 0, pathPos(u)) u = parent of root // вырезали нижний путь и подняли нижнюю вершину до нижней вершины следующего пути root = pathRoot(u) root = pathRoot(v) while not (root is ancestor of u) // аналогично прошлому while, но с другой стороны res += queryPath(getPath(v), 0, pathPos(v)) v = parent of root root = pathRoot(v) // последний путь (тот, что содержит общего предка) обрезан с двух сторон полученными вершинами res += queryPath(path(u), min(pathPos(u), pathPos(v)), max(pathPos(u), pathPos(v))) return res