Задача. Пусть есть
Формализуем эту задачу, представив мальчиков и девочек как вершины в двудольном графе, рёбрами которого будет отношение «могут танцевать вместе».
Паросочетанием
Все вершины, у которых есть смежное ребро из паросочетания (т.е. которые имеют степень ровно один в подграфе, образованном
Мощностью паросочетания назовём количество рёбер в нём. Наибольшим (максимальным) паросочетанием назовём паросочетание, мощность которого максимальна среди всех возможных паросочетаний в данном графе, а совершенным — где все вершины левой доли им насыщенны.
Паросочетания можно искать в любых графах, однако этот алгоритм неприятно кодить, и он работает за
Цепью длины
Чередующейся цепью относительно некоторого паросочетания назовём простой путь длины
Увеличивающей цепью относительно некоторого паросочетания назовём чередующуюся цепь, у которой начальная и конечная вершины не принадлежат паросочетанию.
Здесь красными помечены вершины паросочетания, а в графе есть увеличивающая цепь: $1 \to 8 \to 4 \to 6 \to 3 \to 7$.
Зачем нужны увеличивающие цепи? Оказывается, можно с их помощью увеличивать паросочетание на единицу (отсюда и название). Можно взять такой путь и провести чередование — убрать из паросочетания все рёбра, принадлежащие цепи, и, наоборот, добавить все остальные. Всего в увеличивающей цепи нечетное число рёбер, а первое и последнее были не в паросочетании. Значит, мощность паросочетания увеличилась ровно на единицу.
В примере добавятся синие рёбра
Алгоритм Куна в этом и заключается — будем искать увеличивающую цепь, пока ищется, и проводить чередование в ней. Увеличивающие цепи удобны тем, что их легко искать: можно просто запустить поиск пути из произвольной свободной вершины из левой доли в какую-нибудь свободную вершину правой доли в том же графе, но в котором из правой доли можно идти только по рёбрам паросочетания (то есть у вершин правой доли будет либо одно ребро, либо ноль). Это можно делать как угодно (для упражнения автор рекомендует явно строить такой граф, искать путь и явно проводить чередования), однако устоялась эффективная реализация в виде dfs на 20 строчек кода, приведённая ниже.
const int maxn;
vector<int> g[maxn]; // будем хранить только рёбра из левой доли в правую
int mt[maxn]; // с какой вершиной сматчена вершина правой доли (-1, если ни с какой)
bool used[maxn]; // вспомогательный массив для поиска пути dfs-ом
// dfs возвращает, можно ли найти путь из вершины v
// в какую-нибудь вершину правой доли
// если можно, то ещё и проводит чередование
bool dfs(int v) {
if (used[v])
return false;
used[v] = true;
for (int u : g[v]) {
// если вершина свободна, то можно сразу с ней соединиться
// если она занята, то с ней можно соединиться только тогда,
// когда из её текущей пары можно найти какую-нибудь другую вершину
if (mt[u] == -1 || dfs(mt[u])) {
mt[u] = v;
return true;
}
}
return false;
}
// где-то в main:
memset(mt, -1, sizeof mt);
for (int i = 0; i < n; i++) {
memset(used, 0, sizeof mt);
if (dfs(i))
cnt++;
}
Для доказательства алгоритма нам будет достаточно ещё доказать, что если увеличивающие цепи уже не ищутся, то паросочетание в принципе нельзя увеличить.
Теорема (Бержа). Паросочетание без увеличивающих цепей является максимальным.
Доказательство проведём от противного: пусть есть два паросочетания вершин
Раскрасим ребра из паросочетания, соответствующего
Такой алгоритм ровно
Что примечательно, его можно не бояться запускать на ограничениях и побольше (
-
Eго можно жадно инициализировать (просто заранее пройтись по вершинам левой доли и сматчить их со свободной вершиной правой, если она есть).
-
Можно не заполнять нулями на каждой итерации массив
used
, а использовать следующий трюк: хранить в нём вместо булева флага версию последнего изменения, а конкретно -- номер итерации, на которой это значение сталоtrue
. Если этот номер меньше текущего номера итерации, то мы можем воспринимать это значение какfalse
. В каком-то смысле это позволяет эмулировать очищение массива за константу. -
Очень часто граф приходит из какой-то другой задачи, природа которой накладывает ограничения на его вид. Например, в задачах на решетках (когда есть двумерный массив, и соседние клетки связаны друг с другом) граф двудольный, но степень каждой вершины маленькая, и граф имеет очень специфичную структуру, и на нём алгоритм Куна работает быстрее, чем ожидается из формулы
$n \times m$ . Контрпримеры в таких задачах на самом деле почти всегда можно сгенерировать, но авторы редко так заморачиваются.
Вообще говоря, увлекаться ускорением алгоритма Куна не стоит — существует более асимптотически быстрый алгоритм. Задача нахождения максимального паросочетания — частный случай задачи о максимальном потоке, и если применить алгоритм Диница к двудольным графам с единичной пропускной способностью, то выясняется, что работать он будет за
Сводить задачи к поиску максимального паросочетания обычно не очень трудно, но в некоторых случаях самому додуматься сложно. Разберём одну такую известную задачу. Дан ориентированный ациклический граф
Построим соответствующие изначальному графу
- В каждой доле графа
$H$ будет по$n$ вершин. Обозначим их через$a_i$ и$b_i$ соответственно. - Для каждого ребра
$(i, j)$ исходного графа$G$ проведём соответствующее ребро$(a_i, b_j)$ в графе$H$ . - Теперь из графа
$H$ сделаем граф$\overline{H}$ , добавив обратное ребро$(b_i, a_i)$ для каждого$i$ .
Если мы рассмотрим любой путь
Итак, есть взаимно однозначное соответствие между путями в
Заметим, что если есть
Мы теперь можем свести задачу к нахождению максимального паросочетания в двудольном графе
Лемма Холла (или: теорема о свадьбах) — очень удобный критерий в задачах, где нужно проверить, что паросочетание существует, но при этом не требуется строить его явно.
Лемма Холла. Полное паросочетание существует тогда и только тогда, когда любая группа вершин левой доли соединена с не меньшим количеством вершин правой доли.
Доказательство. В одну сторону понятно — если совершенное паросочетание есть, то для любого подмножества вершин левой доли можно взять вершины правой, соединенные с ним паросочетанием.
В другую сложнее — нужно воспользоваться индукцией. Будем доказывать, что если паросочетание не полное, то можно в таком графе найти увеличивающую цепь, и с её помощью увеличить паросочетание на единицу.
База индукции: одна вершина из
Индукционный переход: пусть после
Рассмотрим множество вершин
Тогда в
Тогда должен существовать путь из
Задача. Дан граф. Назовем вершинным покрытием такое множество вершин, что каждое ребро графа инцидентно хотя бы одной вершине из множества. Необходимо найти вершинное покрытие наименьшего размера.
Следует заметить, что в общем случае это очень сложная задача, но для двудольных графов она имеет достаточно простое решение.
Теорема.
Доказательство.
Алгоритм. Мысленно ориентируем ребра графа: ребра из
Заметим, что граф разбился на несколько множеств:
Понятно, что данное множество покроет все ребра. Осталось выяснить, почему
Упражнение. Подумайте, как это можно применить к решению задачи о нахождении максимального независимого множества.
С весьма большой вероятностью матроиды вам никогда не пригодятся в школьных олимпиадах, однако, если вам совсем нечего делать, можете про них почитать.
Математика вообще занимается тем, что обобщает всякие объекты и старается формулировать все теоремы максимально абстрактно. Так, концепцию хороших подмножеств (паросочетаний) обобщает понятие матроида. Практическое применение — жадный алгоритм Радо-Эдмондса, который нужен для обоснования большого числа жадников, где нужно набрать какое-то подмножество минимального / максимального веса. Сам алгоритм максимально простой: давайте отсортируем эти объекты по весу и будем добавлять их в таком порядке в наше хорошее множество, если оно после добавления остается хорошим.
Применимо к паросочетаниям: пусть у вершин левой доли есть вес, и нам нужно набрать максимальное паросочетание минимального веса. Тогда выясняется, что можно просто отсортировать вершины левой доли по весу и пытаться в таком порядке добавлять их в паросочетание стандартным алгоритмом Куна.