← Все вопросы

Алгоритм Куна для максимального паросочетания в двудольном графе — как и почему работает?

Задан 5 месяцев назад628 просмотров2 ответа
11

Есть двудольный граф: слева работники, справа задачи, ребро = «может выполнить». Нужно максимальное число пар «работник-задача». Знаю, что это решается алгоритмом Куна через чередующиеся пути, но не понимаю механики. Дайте идею + рабочий C++ и асимптотику.

2 ответа

16
✓ Принятый ответ — помог автору

Идея. Перебираем вершины левой доли. Для каждой пытаемся найти увеличивающий путь — чередующуюся цепочку «свободное ребро → ребро паросочетания → свободное → ...», начинающуюся в свободной левой вершине и заканчивающуюся в свободной правой. Если нашли — «переключаем» рёбра вдоль пути, и размер паросочетания растёт на 1. Поиск пути — это DFS с попыткой «потеснить» уже занятые правые вершины.

int n, m; // размеры левой и правой доли
vector<vector<int>> adj; // adj[left] -> список right
vector<int> matchR; // matchR[right] = к какой left приписана, или -1
vector<char> used;

bool tryKuhn(int v){
    for(int to : adj[v]){
        if(used[to]) continue;
        used[to]=1;
        if(matchR[to]==-1 || tryKuhn(matchR[to])){
            matchR[to]=v;
            return true;
        }
    }
    return false;
}
int maxMatching(){
    matchR.assign(m,-1);
    int res=0;
    for(int v=0; v<n; v++){
        used.assign(m,0);
        if(tryKuhn(v)) res++;
    }
    return res;
}

Почему корректно: теорема Бержа — паросочетание максимально тогда и только тогда, когда в графе нет увеличивающего пути. Кун находит их все, значит результат максимален.

Асимптотика: $O(V \cdot E)$ — внешний цикл по $V$ вершинам, каждый запуск DFS обходит до $O(E)$ рёбер. Память $O(V+E)$.

Оптимизация: жадная инициализация (сначала ставим любые свободные пары до запуска основного цикла) часто ускоряет в разы на практике.

6

Частые грабли в реализации Куна:

  1. used для правой доли, не для левой! Массив used отмечает посещённые правые вершины в рамках одного запуска DFS, чтобы не зациклиться. Его нужно обнулять перед каждым новым tryKuhn(v) из внешнего цикла.
  2. matchR индексируется по правой доле. Многие путаются и заводят matchL — можно и так, но классическая версия хранит «кто стоит в паре с правой вершиной».

Если граф очень плотный и нужно быстрее — есть Хопкрофт-Карп за $O(E\sqrt{V})$ (по сути Диниц на единичных пропускных). Но для большинства олимпиадных ограничений Куна с жадной инициализацией хватает.

Ваш ответ

Войдите, чтобы ответить на вопрос.