Бинпоиск по ответу выдаёт WA на единицу: проблема с границами и предикатом
Пишу бинпоиск по ответу, идея правильная, но получаю WA на единицу — ответ то на 1 больше, то на 1 меньше эталона. Подозреваю границы lo/hi или mid. Как отлаживать такие off-by-one в бинпоиске системно?
2 ответа
90% off-by-one в бинпоиске лечатся одним дисциплинированным шаблоном. Зафиксируй инвариант и не отступай от него.
Шаблон «минимальный true» (предикат false...false true...true, ищем первую истину):
int lo = LO, hi = HI; // ответ гарантированно в [lo, hi], check(hi) == true
while (lo < hi) {
int mid = lo + (hi - lo) / 2; // округление ВНИЗ
if (check(mid)) hi = mid; // mid годится — может быть ответом, не отбрасываем
else lo = mid + 1; // mid не годится — отбрасываем строго
}
// lo == hi — искомый минимальный true
Шаблон «максимальный true» (предикат true...true false...false):
while (lo < hi) {
int mid = lo + (hi - lo + 1) / 2; // округление ВВЕРХ — обязательно!
if (check(mid)) lo = mid;
else hi = mid - 1;
}
Чеклист отладки off-by-one:
- Округление mid. В «минимальном true» — вниз. В «максимальном true» — вверх (
+1). Перепутал — бесконечный цикл или off-by-one. - Куда включается mid. Если
check(mid)истинно и mid — кандидат, нельзя писатьhi = mid - 1(потеряешь ответ). Должно бытьhi = mid. - Начальные границы. Проверь, что искомое значение внутри
[lo, hi]и чтоcheckна крайних значениях ведёт себя как ждёшь (граница «всегда true»/«всегда false» включена). - Сам предикат. Часто WA не в поиске, а в
check: проверь её на тех самых LO и HI вручную.
Отлаживай так: распечатай check(x) для всех x в маленьком тесте — должна быть ровно одна точка перехода. Если переходов несколько — предикат немонотонен, бинпоиск неприменим в принципе.
Ещё mid = (lo + hi) / 2 может переполниться, если lo и hi около INT_MAX (например, бинпоиск по значению до 2e9). Всегда пиши mid = lo + (hi - lo) / 2 — это не переполняется. И если границы — long long, держи mid тоже long long. Классический скрытый баг: lo/hi объявлены int, а сумма вылезает за диапазон, mid становится отрицательным, RE или мусор.