Паросочетание в произвольном графе (8-е марта 2024)
- Что мы уже знаем?
- Lm о дополняющем пути (для произвольного графа верна).
- Алгоритм Куна и теорема о корректности (двудольностью не пользовались).
- dfs/bfs для поиска чередующегося пути.
- M1 xor M2 (двух паросочетаний), M1 xor P (паросочетание + путь, чётный или нечётный)
- Алгоритм Эдмондса
- for v=1..n запустим dfs/bfs(v) от Куна для поиска пути.
- Поиск пути нашёл путь или нечётный цикл со стеблем (соцветие = цветок + стебель).
- Сожмём нечётный цикл в одну вершину, продолжим поиск. На слове "продолжим" мы понимаем, что из dfs/bfs удобнее bfs.
- Корректность: путь ∃ в исходном графе ⇔ путь ∃ в сжатом графе (в одну сторону предъявляем алгоритм расжатия в другую разбираем случаи, как выглядел исходный путь)
- Простейшая реализация: создаём новую вершину за O(cycleLen*n), кидаем в очередь, делаем рекурсивный вызов continueBfs, который возвращает путь, если путь проходит через сжатую вершину, делаем расжатие.
- Габов: красивая простая реализация алгоритма Эдмондса за O(n3)
- mate[], p[], восстановление пути.
- Все вершины исходного графа, достижимые как вершины первой доли, образуют дерево по отношению v &arr; p[mate[v]].
- Явно сжимать ничего не будем. base[v] = основание цикла, в котором сейчас лежит v, изначально base[v] = v
- Сжатие цикла (x,y)
- За O(k) находим b=LCA(x,y) в v → p[mate[v]].
- Проходим части пути x → first x1 : base[x1] = b и y → first y1 : base[y1] = b, проставляем новые ссылки p[], добавляем вершины в очередь, ∀ t на пути помечаем mark[base[t]]=true.
- За O(n) перебираем все вершины i и:
if mark[base[i]] then mark[base[i]] := b
.
- Корректность обновления ссылок: путь x &rarr x1 и y → y1 не пересекаются по вершинам ⇒ рассмотрим нечётный цикл y1-y-x-x1, ото всех вершин мы по ссылкам теперь можем дойти до v: base[v]=b, а от v точно можем дойти до b (т.к. ссылки от w: base[w]=b не меняли).
- Почему нельзя идти до b? Тогда пути могли бы пересекаться и ссылки зациклятся (сместим x1 и y1 на цикле по чётности, куски пути x1 → b и y1 → b будут пересекаться)
- Картинка "сжатие уже сжатых циклов" (цикл циклов)
- Оптимизации базовой реализации за V3
- Не обнулять пометки в Куне: V3 → |M|V2
- Жадная инициализация: нашли паросочетание размера ≥ |M|/2, соптимизили предыдущее в ≥ 2 раз
- Прикрутить DSU, чтобы дойти до O(nmα)
- LCA можно искать за O(kα), а можно писать dfs и получать LCA за O(1)
- Сжимать цикл можно за O(kα) (v → p[getBase(v)])
- Расставлять новые p[] быстрее не умеем: нам недостаточно вершины getBase(v), нам нужна ещё и предыдущая. Но для определения "есть ли путь" p[] от внутренних вершин цикла и не нужны, нужно только поддерживать p[base].
- Если хотим восстановить путь, можно это делать не благодаря p[], а уже в конце.
- Задача: поиск минимального по весу простого чётного пути.