Введение

Жадный алгоритм — это алгоритм, который на каждом шагу делает локально наилучший выбор в надежде, что итоговое решение будет оптимальным.

Сначала разберём базовые задачи, а затем осознаем всю мощь "жадных алгоритмов".

Задача об отрезках

Даны n отрезков. Отрезок задаётся двумя границами — началом и концом. Вам нужно выбрать как можно больше отрезков таким образом, чтобы ни один из них не пересекался с другим.

К решение проще приступить таким образом : нарисуйте n горизонтальных линей. Нарисуйте каждый отрезок на соответствующей прямой, просмотрите слева направо их. Когда вы выбираете какой-то отрезок, выделите его цветом, а все которые с ним пересекаются сотрите. Подумайте.

Возможно вы догадались до такой идеи — каждый раз брать отрезок у которого правая граница минимальная. Затем стирать все с ним пересекающиеся.

Это очевидно, по сути вы каждый раз выбираете отрезок, который стирает наименьшее количество отрезков. Но почему-бы не выбрать какой-то отрезок в центре?

Докажем нашу жадную стратегию брать отрезки, у которых правая граница минимальная. Доказывать жадный алгоритм не обязательно, но иногда интуиция подводит, и вы придумали не верный жадный алгоритм или задача вообще не решалась жадным алгоритмом.

Доказательство

Пусть наше жадное решение выбрало множество из k отрезков , но на самом деле оптимальное решение выбрало m отрезков .

Докажем, что . По предположению выше .

Отсортируем все отрезки и по возрастанию правой границы, а в случае равенства по убыванию левой. Пусть первый индекс, где отрезки не совпадают (.

Пусть существует. Если нет, то прочтите текст в рамке второго случая.

Сравним и .

  1. Случай не возможен по определению нашего алгоритма.

  2. Случай .

Заметим, что замена -го отрезка в на ничего не сломает, а возможно только увеличит количество взятых отрезков. Заменим отрезок. Заметим, что можно опять заменить другие отрезки, так как мы не ухудшаем ответ. Пришли к тому, что теперь не существует.

Первые отрезков в совпадают со всеми из , а из этого следует, что , так как в противном случае, жадное решение взяло бы ещё отрезки.

  1. Случай возможен, но доказательство для левой границы аналогично.

Следовательно — корректность жадного решения доказана.

Общее доказательство для любых жадных алгоритмов аналогично.

Код :

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).

Это задача имеет достаточно простое решение — динамическое программирование. Мы рассмотрим решение такой и подобных задач немного позже.

Last change: 2023-10-16, commit: 15a8cc9