EduBrick

Поиск по ответу

Как свести незнакомую задачу к массиву из нулей и единиц — и три приметы, по которым это узнаётся в условии.

4 мин

Бинарный поиск по массиву — частный случай. Общий выглядит так.

Есть функция check(x), которая для маленьких xx возвращает false, а начиная с какого-то момента — всегда true. Иначе говоря, воображаемый массив её значений выглядит как

0 0 0 0 0 1 1 1 1 1

Найти границу между нулями и единицами — это ровно тот же бинарный поиск, только вместо a[mid] вызывается check(mid).

long long l = -1, r = LIMIT;    // check(l) ложно, check(r) истинно
while (r - l > 1) {
    long long mid = l + (r - l) / 2;
    if (check(mid)) r = mid;
    else l = mid;
}
// r — наименьшее значение, при котором check истинно

Массива здесь нет вовсе. Есть только функция и обещание, что она монотонна.

Три приметы в условии

Задача решается поиском по ответу, когда сходятся три вещи:

  1. Спрашивается «наименьшее XX, при котором получится» или «наибольшее XX, при котором ещё хватит».
  2. Проверить конкретное XX легко, а найти его прямо — непонятно как.
  3. Ответ монотонен: если при XX получилось, то при всех больших (или всех меньших) тоже.

Третий пункт нужно проговаривать вслух, а не считать очевидным. Именно на нём ломаются задачи, где монотонности нет: скажем, «раздать поровну» монотонностью не обладает, и поиск по ответу там даст неверный результат, а не медленный.

Как выглядит check

Проверка почти всегда — жадный проход или формула.

Развезти ящики за dd дней машиной грузоподъёмности XX. Грузим подряд, пока влезает; не влезло — новый день. Считаем дни, сравниваем с dd.

bool check(long long capacity) {
    int days = 1;
    long long current = 0;
    for (long long weight : boxes) {
        if (weight > capacity) return false;   // не увезём вообще
        if (current + weight <= capacity) current += weight;
        else { days++; current = weight; }
    }
    return days <= d;
}

Нарезать kk кусков длины LL из имеющихся верёвок. Сумма li/L\lfloor l_i / L \rfloor сравнивается с kk.

Успеют ли станки сделать nn деталей за TT минут. Сумма T/ti\lfloor T / t_i \rfloor сравнивается с nn.

Общее у всех трёх: проверка линейна, а перебор ответа стоил бы линии на каждое значение.

Границы поиска

Нижнюю границу берут не «ноль на всякий случай», а такую, при которой проверка осмысленна. В задаче про грузоподъёмность машина легче самого тяжёлого ящика не увезёт ничего, и жадный проход на таком значении либо соврёт, либо зациклится. Значит, нижняя фиктивная граница — «самый тяжёлый ящик минус один».

Верхнюю берут заведомо достаточной, но не «на всякий случай огромной»: чем шире промежуток, тем больше шагов, а при вычислениях внутри check большие числа ещё и переполняются.

Когда монотонность спрятана

Интереснее всего задачи, где монотонность есть, но не та, про которую спрашивают.

Минимум максимума. Даны неубывающий массив AA и невозрастающий BB; нужно kk, при котором max(Ak,Bk)\max(A_k, B_k) минимален. Сам максимум по kk не монотонен — он сначала падает, потом растёт. Зато монотонна разность AkBkA_k - B_k: она не убывает. Бинарным поиском находим первое kk, где AkBkA_k \ge B_k, и проверяем два соседних варианта.

Общий приём: если функция «падает, потом растёт», ищите монотонную величину, которая меняет знак в точке минимума.

Отношение. Максимизировать viwi\frac{\sum v_i}{\sum w_i} по подмножеству из kk элементов. Набор с отношением не меньше λ\lambda существует тогда и только тогда, когда максимум суммы viλwiv_i - \lambda w_i по kk элементам неотрицателен — а это просто «взять kk наибольших». Предикат монотонен по λ\lambda, и поиск работает.

Чётность. В отсортированном массиве все числа встречаются дважды, кроме одного. Само значение ничего не подсказывает, а вот позиции — да: до одинокого элемента пары стоят на местах (1,2),(3,4),(1,2), (3,4), \ldots, а после него сдвигаются. Предикат «пара с номером tt ещё не сдвинута» монотонен.