Семь задач на поиск по ответу
Разбор постановок, которые встречаются чаще всего: коровы в стойла, провода, встреча в точке, принтеры, шарики, выборы. Для каждой — предикат и границы.
7 мин
Схема поиска по ответу описана отдельно. Здесь — семь конкретных задач. Смысл не в самих условиях, а в том, чтобы натренировать шаг «придумать предикат»: именно на нём задачи и проигрываются.
Для каждой указано три вещи: что бинарим, как проверяем, где границы.
1. Коровы в стойла
На прямой стоят стойл с координатами . Нужно расставить коров так, чтобы минимальное расстояние между соседними было максимальным.
Формулировка «максимизировать минимум» — самая надёжная примета поиска по ответу.
Бинарим по расстоянию . Предикат: «можно расставить коров, чтобы все попарные расстояния были не меньше ». Проверка жадная — ставим первую корову в самое левое стойло и дальше каждую следующую в первое подходящее:
bool fits(const vector<long long>& x, int k, long long d) {
int count = 1;
long long last = x[0];
for (size_t i = 1; i < x.size(); i++)
if (x[i] - last >= d) { count++; last = x[i]; }
return count >= k;
}
Жадность корректна: если корову можно поставить сейчас, откладывать её невыгодно — правее места только меньше.
Границы: (всегда можно), (заведомо нельзя, если коров хотя бы две). Ответ — в l.
2. Провода
Есть кусков провода длиной . Нужно нарезать хотя бы кусков одинаковой длины, как можно более длинных.
Бинарим по длине куска . Проверка в одну строку: из провода длины получается кусков.
long long total = 0;
for (long long length : a) total += length / d;
return total >= k;
Границы: , . Ответ в l. Отдельно обработайте случай, когда ответ нулевой, — обычно требуется вывести .
Ловушка: если длины вещественные (метры с сантиметрами), домножьте на и работайте в целых. Вещественный поиск здесь заведомо хуже.
3. Встреча в одной точке
человек стоят в точках и двигаются со скоростями . За какое минимальное время все смогут собраться в одной точке?
Бинарим по времени . За время человек достигает любой точки отрезка . Все встретятся тогда и только тогда, когда у этих отрезков есть общая точка.
Пересечение отрезков — максимум левых концов против минимума правых:
double lo = -1e18, hi = 1e18;
for (int i = 0; i < n; i++) {
lo = max(lo, x[i] - v[i] * t);
hi = min(hi, x[i] + v[i] * t);
}
return lo <= hi;
Монотонность очевидна: с ростом отрезки только расширяются, и однажды пересёкшись, разойтись уже не могут.
Здесь ответ вещественный — значит, вещественный поиск с фиксированным числом итераций.
4. Два принтера
Первый принтер печатает лист за минут, второй за . Нужно напечатать листов. За какое минимальное время?
Задача решается формулой, но выводить её лень, а ошибиться легко. Бинарный поиск избавляет от этого целиком.
Бинарим по времени . За минут первый напечатает , второй :
return t / x + t / y >= n;
Границы: , — этого хватит гарантированно. Ответ в r, потому что минимизируем.
Это общий приём: вместо вывода формулы напишите проверку. Проверка почти всегда очевидна, а формула — нет. Логарифм сверху обычно можно себе позволить.
5. Воздушные шарики
помощников надувают шарики. Помощник работает минут, надувая шариков, потом отдыхает минут, и так по кругу. Нужно шариков. За какое минимальное время?
Бинарим по времени . Для каждого помощника считаем, сколько он успеет: полных циклов , плюс остаток, который надо аккуратно поделить между работой и отдыхом:
long long cycle = t[i] + y[i];
long long full = T / cycle;
long long rest = min(T % cycle, t[i]); // в остатке он работает не дольше t[i]
total += full * z[i] + rest * z[i] / t[i];
Два подвоха. Первый — min в остатке: если остаток попал на отдых, лишнего не начислится. Второй — задача обычно просит вывести точное распределение, кто сколько надул, а за время помощники успевают больше нужного. Лишнее приходится отрезать отдельным жадным проходом. На этом и падают решения: время найдено верно, а восстановление ответа неверное.
6. Выборы
За кандидата голосует человек, и подкуп одного избирателя стоит одну монету. Для каждого кандидата надо сказать, сколько монет нужно, чтобы он победил (у него строго больше голосов, чем у любого другого).
Здесь поиск по ответу вложен во внешний цикл, и наивно получается медленно.
Фиксируем кандидата и бинарим по — числу голосов, с которым он победит; заведомо . Проверка: у всех остальных должно стать меньше , значит у кандидата с нужно отобрать голосов. Сумма таких изъятий — обязательные траты . Если , бюджета хватает, и недостающие голоса докупаются у кого угодно.
Прямая проверка — , итого на всех кандидатов. Слишком медленно при .
Ускорение: отсортируем и посчитаем суффиксные суммы. Тогда те, у кого , — это суффикс отсортированного массива, его начало ищется lower_bound за логарифм, а сумма изъятий берётся готовой:
где — первый индекс с . Проверка стала , всё решение — .
Самого кандидата из суффикса исключать не нужно: раз , он в него не попадает. Это тот случай, когда проверка формулы перебором на маленьких данных экономит час отладки.
Это типичный второй этап таких задач: сначала придумать проверку, потом ускорить её предподсчётом. Придумывать сразу быструю проверку почти никогда не нужно.
7. Дипломы
Нужно напечатать дипломов прямоугольниками на квадратном листе стороны . Найдите минимальное .
Бинарим по стороне. На лист стороны помещается дипломов:
return (s / w) * (s / h) >= n;
Единственная сложность — переполнение. При до произведение двух частных не помещается никуда. Спасает ранний выход: если первый множитель уже не меньше , ответ положителен без умножения.
long long first = s / w, second = s / h;
if (first == 0 || second == 0) return false;
if (first >= n || second >= n) return true;
return first * second >= n; // оба меньше n — произведение не переполнится
Что общего
Во всех семи задачах порядок действий один:
- Назвать величину, которую бинарим, — это всегда ответ или его часть, а не индекс.
- Записать предикат словами и убедиться, что он монотонен.
- Найти границы, где предикат заведомо ложен и заведомо истинен, — с запасом.
- Написать проверку максимально прямолинейно.
- Только если не проходит по времени — ускорять проверку.
Шаг 2 пропускают чаще всего, и именно он ломает решение на закрытых тестах.