diff --git a/content/russian/cs/spanning-trees/dcp.md b/content/russian/cs/spanning-trees/dcp.md index bbbe816d..f7a20cb1 100644 --- a/content/russian/cs/spanning-trees/dcp.md +++ b/content/russian/cs/spanning-trees/dcp.md @@ -1,12 +1,12 @@ --- title: Динамическая связность authors: -- Сергей Слотин -date: 2021-09-25 + - Сергей Слотин +date: 2021-09-25T00:00:00.000Z prerequisites: -- /cs/set-structures/dsu -- /cs/persistent/persistent-array -- /cs/decomposition/rollback + - /cs/set-structures/dsu + - /cs/persistent/persistent-array + - /cs/decomposition/rollback --- В контексте графов, система непересекающихся множеств напрямую решает следующую задачу: @@ -37,17 +37,17 @@ prerequisites: ### Divide-and-conquer по запросам -Давайте вместо корневой эвристики заведем рекурсивную функцию `solve(l, r)`, которая будет отвечать на все запросы с $l$ по $r$, имея СНМ, соответствующий всем ребрам, которые существуют на всем этом промежутке. +Давайте вместо корневой эвристики заведём дерево отрезков, в вершинах которого сохраним все рёбра, существующие целиком на всём отрезке этой вершины и не существующие целиком на отрезке её предка. Таким образом каждое ребро окажется не более чем в $log(n)$ вершинах ДО. -Эта функция будет действовать следующим образом: +Рёбра, имеющие несколько отрезков жизни можно считать разными рёбрами и добавлять в ДО независимо, т. к. эти отрезки не пересекаются, а также их суммарное количество будет не более $n$. -0. Если в промежутке всего один запрос, то найдем ответ на него через СНМ и выйдем. В противном случае: -1. Разделим промежуток времени пополам: `t = (l + r / 2)`. -2. Рекурсивно разрешим левую половину: `solve(l, t)`. -3. Добавим в СНМ те ребра, которые существуют на всей правой половине запросов. -4. Рекурсивно запустимся от правой половины: `solve(t, r)`. -5. Откатим СНМ до изначального состояния. +Чтобы найти ответ, можно пройтись по дереву отрезков такой `dfs(v)`: -Так как мы всегда поддерживаем инвариант «когда мы запускаемся и выходим из рекурсии, СНМ всегда чистый для этого промежутка», алгоритм действительно ответит на все запросы и будет работать за $O(n \log^2 n)$. +0. Добавим в СНМ рёбра из тек. вершины ДО $v$. +1.0. Если отрезок единичной длины, ответим на все его запросы, +1.1. Иначе запустимся в левого, а затем и в правого ребёнка. +2. Откатим все добавленные из тек. вершины $v$ в СНМ рёбра. + +Несложно заметить, что асимптотика алгоритма $O(n \log^2 n)$, потому что каждое из не более $n$ рёбер мы добавим в не более чем $log(n)$ отрезком ДО, в каждый отрезок зайдём ровно по 1 разу и каждое ребро из него добавим в СНМ за $O(log(n))$. Заметим, что мы нигде не использовали ничего конкретно про связность — можно отвечать на любые запросы, поддерживаемые СНМ, например о размерах компонент или числе ребер. Также существуют модификации для других задач, например для нахождения мостов или компонент двусвязности — подробнее можно почитать в [дипломной работе](http://se.math.spbu.ru/SE/diploma/2012/s/Kopeliovich_diploma.pdf) Сергея Копелиовича.