Введение
Жадный алгоритм — это алгоритм, который на каждом шагу делает локально наилучший выбор в надежде, что итоговое решение будет оптимальным.
Сначала разберём базовые задачи, а затем осознаем всю мощь "жадных алгоритмов".
Задача об отрезках
Даны
n
отрезков. Отрезок задаётся двумя границами — началом и концом. Вам нужно выбрать как можно больше отрезков таким образом, чтобы ни один из них не пересекался с другим.
К решение проще приступить таким образом : нарисуйте n
горизонтальных линей. Нарисуйте каждый отрезок на соответствующей прямой, просмотрите слева направо их. Когда вы выбираете какой-то отрезок, выделите его цветом, а все которые с ним пересекаются сотрите. Подумайте.
Возможно вы догадались до такой идеи — каждый раз брать отрезок у которого правая граница минимальная. Затем стирать все с ним пересекающиеся.
Это очевидно, по сути вы каждый раз выбираете отрезок, который стирает наименьшее количество отрезков. Но почему-бы не выбрать какой-то отрезок в центре?
Докажем нашу жадную стратегию брать отрезки, у которых правая граница минимальная
. Доказывать жадный алгоритм не обязательно, но иногда интуиция
подводит, и вы придумали не верный жадный алгоритм
или задача вообще не решалась жадным алгоритмом.
Доказательство
Пусть наше жадное
решение выбрало множество из k
отрезков , но на самом деле оптимальное решение выбрало m
отрезков .
Докажем, что . По предположению выше .
Отсортируем все отрезки и по возрастанию правой границы, а в случае равенства по убыванию левой. Пусть первый индекс, где отрезки не совпадают (.
Пусть существует. Если нет, то прочтите текст в рамке второго случая.
Сравним и .
-
Случай не возможен по определению нашего алгоритма.
-
Случай .
Заметим, что замена -го отрезка в на ничего не сломает, а возможно только увеличит количество взятых отрезков. Заменим отрезок. Заметим, что можно опять заменить другие отрезки, так как мы не ухудшаем
ответ. Пришли к тому, что теперь не существует.
Первые отрезков в совпадают со всеми из , а из этого следует, что , так как в противном случае, жадное решение взяло бы ещё отрезки.
- Случай возможен, но доказательство для левой границы аналогично.
Следовательно — корректность жадного решения доказана.
Общее доказательство для любых жадных алгоритмов аналогично.
Код :
void solve() {
int n;
cin >> n;
vector<pair<int, int>> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i].first >> a[i].second;
}
sort(a.begin(), a.end(), [&](auto& x, auto& y) {
return make_pair(x.second, -x.first) < make_pair(y.second, -y.first);
});
int ans = 0;
int r = INT_MIN;
for (int i = 0; i < n; i++) {
if (r < a[i].first) {
r = a[i].second;
ans++;
}
}
cout << ans;
}
Задачи на которых не работает
Задача о размене монет (это задача о рюкзаке)
Есть набор монет с разными номиналами, и вам нужно разменять заданную сумму минимальным количеством монет.
Например, в нашей выдуманной стране номиналы . Разменять 5
можно одной 5
, 13
на две 3
и 10
и тп.
Возможно, вы уже поняли алгоритм — выдавать сначала монету наибольшего номинала, пока можем. Потом выдаём наибольшим из оставшихся номиналов и так далее.
НО, если у нас и нам надо разменять , то оптимальнее начать не с , а с (
9 + 9 = 18
).
Это задача имеет достаточно простое решение — динамическое программирование. Мы рассмотрим решение такой и подобных задач немного позже.