Heavy-light декомпозиция — различия между версиями
| Строка 37: | Строка 37: | ||
| Хоть это и не самый эффективный способ для решения этой задачи, но можно заметить, что с помощью такого трюка мы можем решить широкий класс задач просто сведя её к задаче о запросах в дереве отрезков (такие как максимум на пути, покраска на пути, прибавление на пути и др.). | Хоть это и не самый эффективный способ для решения этой задачи, но можно заметить, что с помощью такого трюка мы можем решить широкий класс задач просто сведя её к задаче о запросах в дереве отрезков (такие как максимум на пути, покраска на пути, прибавление на пути и др.). | ||
| ==Реализация== | ==Реализация== | ||
| − | Опущены некоторые детали реализации: построение и предподсчёт. <tex>getPath</tex> {{---}} функция, позволяющая найти номер пути, относящийся к конкретной вершине, <tex>pathRoot</tex> {{---}} корень пути (самая верхняя вершина), <tex> | + | Опущены некоторые детали реализации: построение и предподсчёт. <tex>getPath</tex> {{---}} функция, позволяющая найти номер пути, относящийся к конкретной вершине, <tex>pathRoot</tex> {{---}} корень пути (самая верхняя вершина), <tex>pathPos</tex> {{---}} смещение вершины в пути относительно корня пути, <tex>getValue(i, j)</tex> {{---}} вес <tex>j</tex>-ого ребра в <tex>i</tex>-ом пути. | 
| Пример реализации запроса суммы на пути: | Пример реализации запроса суммы на пути: | ||
| Строка 57: | Строка 57: | ||
|       '''int''' root = pathRoot(u) |       '''int''' root = pathRoot(u) | ||
|       '''while''' !isAncestor(root, v) <font color=green>// поднимаемся до тех пор, пока наш путь не содержит общего предка u и v</font> |       '''while''' !isAncestor(root, v) <font color=green>// поднимаемся до тех пор, пока наш путь не содержит общего предка u и v</font> | ||
| − |           res += queryPath(getPath(u), 0,  | + |           res += queryPath(getPath(u), 0, pathPos(u)) | 
|           u = getParent(root)   <font color=green>// вырезали нижний путь и подняли нижнюю вершину до нижней вершины следующего пути</font> |           u = getParent(root)   <font color=green>// вырезали нижний путь и подняли нижнюю вершину до нижней вершины следующего пути</font> | ||
|           root = pathRoot(u) |           root = pathRoot(u) | ||
|       root = pathRoot(v) |       root = pathRoot(v) | ||
|       '''while''' !isAncestor(root, u) <font color=green>// аналогично прошлому while, но с другой стороны</font> |       '''while''' !isAncestor(root, u) <font color=green>// аналогично прошлому while, но с другой стороны</font> | ||
| − |           res += queryPath(getPath(v), 0,  | + |           res += queryPath(getPath(v), 0, pathPos(v)) | 
|           v = getParent(root) |           v = getParent(root) | ||
|           root = pathRoot(v) |           root = pathRoot(v) | ||
|       <font color=green>// последний путь (тот, что содержит общего предка) обрезан с двух сторон полученными вершинами</font> |       <font color=green>// последний путь (тот, что содержит общего предка) обрезан с двух сторон полученными вершинами</font> | ||
| − |       res += queryPath(path | + |       res += queryPath(path(u), min(pathPos(u), pathPos(v)), max(pathPos(u), pathPos(v))) | 
|       '''return''' res |       '''return''' res | ||
| </code> | </code> | ||
Версия 09:21, 8 июня 2015
Heavy-light декомпозиция — техника разбиения дерева на множество путей для решения задач о запросах на пути в дереве (в том числе с модификациями).
Содержание
Описание задачи
| Задача: | 
| Пусть у нас есть дерево c вершинами и нам нужно проводить операции на нем на пути от вершины до вершины . (Например сумма на пути с модификацией прибавления на пути) | 
Множество подобных запросов делаются за время с помощью Heavy-light декомпозиции.
Описание декомпозиции
Декомпозиция заключается в классификации всех рёбер дерева в 2 вида: легкие и тяжёлые. Введём функцию , которая будет обозначать размер поддерева вершины .
Тяжёлые ребра — ребра такие, что .
Лёгкие ребра — соответственно все остальные.
Очевидно, что из вершины может выходить как максимум одно тяжёлое ребро, т.к. иначе у нас есть два поддерева по как минимум вершин, а также сама вершина . Итого вершин, тогда как у нас всего вершин в поддереве.
Теперь рассмотрим вершины, из которых не ведет ни одно тяжёлое ребро. Будем идти от них вверх до корня или пока не пройдем легкое ребро. Получится какое-то множество путей. Утверждается, что оно будет являться искомым.
Существует вариант Heavy-light декомпозиции на вершинно-непересекающихся путях. Чтобы получить такой путь нужно всего-лишь выкинуть последнее ребро из всех путей в рёберно-непересекающейся декомпозиции.
Доказательство корректности полученной декомпозиции
| Утверждение: | 
| Полученная декомпозиция является искомой. | 
| Докажем по отдельности корректность декомпозиции. 
 | 
Применение
Сумма на пути
Классическая задача о сумме на пути в дереве с решается с помощью Heavy-light декомпозиции за время. Возможны модификации веса.
Построим дерево отрезков над каждым путём. Рассмотрим запрос . Давайте найдем вершину , которая является (например с помощью двоичного подъема. Мы разбили запрос на два: и , на каждый из которых можно легко ответить выбирая путь, содержащий самую нижнюю вершину и вырезать его, пока этот путь не содержит .
Так мы сможем получать ответ на пути за . А всего таких путей нужно будет рассмотреть . Итого мы способны решить эту задачу за время .
Хоть это и не самый эффективный способ для решения этой задачи, но можно заметить, что с помощью такого трюка мы можем решить широкий класс задач просто сведя её к задаче о запросах в дереве отрезков (такие как максимум на пути, покраска на пути, прибавление на пути и др.).
Реализация
Опущены некоторые детали реализации: построение и предподсчёт. — функция, позволяющая найти номер пути, относящийся к конкретной вершине, — корень пути (самая верхняя вершина), — смещение вершины в пути относительно корня пути, — вес -ого ребра в -ом пути.
Пример реализации запроса суммы на пути:
function queryPath(int path, int from, int to):
    int res = 0
    while from <= to:
        if from % 2 == 1:
            res += getValue(path, from)
        if to % 2 == 0:
            res += getValue(path, to)
        to = (to - 1) / 2
        from = (from + 1) / 2
    return res
function query(int u, int v):
    int res = 0
    int root = pathRoot(u)
    while !isAncestor(root, v) // поднимаемся до тех пор, пока наш путь не содержит общего предка u и v
        res += queryPath(getPath(u), 0, pathPos(u))
        u = getParent(root)   // вырезали нижний путь и подняли нижнюю вершину до нижней вершины следующего пути
        root = pathRoot(u)
    root = pathRoot(v)
    while !isAncestor(root, u) // аналогично прошлому while, но с другой стороны
        res += queryPath(getPath(v), 0, pathPos(v))
        v = getParent(root)
        root = pathRoot(v)
    // последний путь (тот, что содержит общего предка) обрезан с двух сторон полученными вершинами
    res += queryPath(path(u), min(pathPos(u), pathPos(v)), max(pathPos(u), pathPos(v)))
    return res

