Доказательство обменом
Как доказать жадность, не зная, что делает оптимальный алгоритм. Разбор на задаче о расписании.
4 мин
Доказательства жадных алгоритмов кажутся абстрактными, потому что доказывать приходится про алгоритм, которого мы не видели. Есть приём, который делает это почти механическим.
Задача о расписании
Дано заявок на переговорную, каждая занимает промежуток . Две заявки нельзя удовлетворить вместе, если промежутки пересекаются. Найти наибольшее число заявок.
Жадный алгоритм: отсортировать по правому концу и идти слева направо, беря заявку, если она не конфликтует с последней взятой.
sort(segments.begin(), segments.end(),
[](auto& x, auto& y) { return x.second < y.second; });
int taken = 0, lastEnd = INT_MIN;
for (auto& [from, to] : segments)
if (from > lastEnd) { taken++; lastEnd = to; }
Почему по правому концу, а не по левому и не по длине? Ответ даёт обмен.
Оптимальный алгоритм как чёрный ящик
Введём оптимальный алгоритм — воображаемую чёрную коробку, которая на любом входе выдаёт наилучший возможный ответ. Мы не знаем, как она устроена и по каким правилам выбирает. Мы знаем только два свойства:
- её ответ допустим;
- лучше её ответа не бывает.
Второе свойство особенно полезно: если рассуждение привело к тому, что чей-то ответ лучше оптимального, значит, где-то была неверная предпосылка. Это готовый механизм доказательства от противного.
Сам обмен
Пусть оптимальный ответ и наш ответ упорядочены по правому концу. Если они совпадают — доказывать нечего. Иначе посмотрим на первое место, где они различаются.
Всё, что до этого места, у них общее. Значит, обе заявки — и та, что взял оптимальный, и та, что взяли мы, — совместимы со всем общим началом.
Наш алгоритм на этом шаге взял заявку с наименьшим правым концом среди доступных. Значит, наша заканчивается не позже, чем взятая оптимальным.
Теперь заменим в оптимальном ответе его заявку на нашу. Что изменится?
- С предыдущими она не конфликтует — они общие, и наш алгоритм её принял.
- Со следующими не конфликтует тоже: они начинались после конца заменённой заявки, а наша заканчивается не позже.
- Размер ответа не изменился.
Получился другой оптимальный ответ, у которого общее начало с нашим на одну заявку длиннее.
Повторяя обмен, дотягиваем общее начало до конца нашего ответа. Значит, существует оптимальный ответ, совпадающий с нашим на всём его протяжении.
Остаётся длина
А вдруг оптимальный длиннее — в нём есть заявка, которой у нас нет?
Пусть после всех обменов префиксы совпали, а у оптимального есть ещё одна заявка. Тогда наш алгоритм её тоже рассматривал — он перебирает все заявки по возрастанию правого конца. И она ничему не мешала, ведь в оптимальном ответе она стоит рядом с тем же префиксом. Значит, наш алгоритм обязан был её взять. Противоречие.
Ответы совпали по размеру, а размер — это и есть ответ задачи.
Схема, которую можно повторять
Приём общий и почти всегда выглядит одинаково:
- Взять оптимальный ответ и наш, упорядочить одинаково.
- Найти первое место, где они расходятся.
- Показать, что выбор нашего алгоритма можно подставить в оптимальный, не ухудшив его.
- Заключить по индукции, что наш ответ не хуже.
Ключевой — третий шаг, и именно в нём используется то самое свойство, по которому мы сортировали. Если подстановка не проходит, значит, ключ сортировки выбран неверно, и это сигнал искать контрпример.
Почему не по длине и не по левому концу
Попробуйте провести тот же обмен для сортировки по длине — он не пройдёт, и контрпример находится сразу. Заявки , , : самая короткая — , она перекрывает стык двух других. Жадность по длине берёт одну заявку, правильный ответ — две.
Мешает не длина, а положение. Именно поэтому сортировать надо по тому, что мешает будущим заявкам, — по правому концу.