Что тренируем сегодня
На прошлой тренировке мы искали ответ по прямой формуле и префиксным суммам на отсортированном массиве. Сегодня — следующий шаг: что делать, когда массив исходно не отсортирован, но из него можно построить вспомогательный массив, который отсортирован всегда — и по нему уже искать ответ бинарным поиском. Вторая задача сессии — про решето Эратосфена, но не в чистом виде «найди все простые до N», а как быстрый способ отвечать на десятки тысяч вопросов о числах до . Это предпоследняя тренировка «разогрева»: после сегодняшней сессии останется всего одна, которая станет мостиком к более длинным сессиям «плато».
Теория
Бинарный поиск по вспомогательному массиву, а не по исходному. Если сам массив не отсортирован (и сортировать нельзя — порядок важен для ответа), из него можно построить другой массив, который отсортирован всегда, и искать уже по нему.
Когда применять:
- вопрос звучит как «до какого места можно дойти» или «докуда достижимо»;
- исходные данные идут в произвольном порядке, но их накопительная характеристика (например, максимум слева направо) растёт монотонно.
Как решать:
- Строим вспомогательный массив за один проход — например, префиксный максимум: на позиции храним наибольшее значение среди первых элементов.
- Такой массив всегда неубывающий (максимум не может уменьшиться при добавлении элементов).
- Применяем бинарный поиск уже к вспомогательному массиву, а не к исходному.
Где ошибаются: пытаются бинарно искать прямо по исходному неотсортированному массиву — без построения монотонного вспомогательного массива результат непредсказуем.
Решето Эратосфена как инструмент быстрых проверок, а не только «найти все простые». Решето можно строить не только для задачи «выведи все простые до N» — им удобно быстро отвечать на много вопросов о простоте чисел.
Когда применять:
- числа в условии большие (например, до );
- но интересующее свойство (например, простота корня числа) сводится к диапазону, который решетом реально покрыть (до , если исходные числа — квадраты).
Как решать:
- Строим решето один раз — но только до той границы, которая реально нужна (например, до ), а не до границы исходных чисел.
- Каждая проверка простоты дальше — это просто чтение готового значения за .
Где ошибаются: строят решето до границы исходных чисел вместо границы, которая реально нужна — это было бы избыточно по памяти и времени, а часто и вовсе невозможно.
Задача 1 — CF ~1200 — «Scuza»
Что дано
Лестница из ступеней. Ступень выше предыдущей на метров (первая ступень выше земли на метров, земля — на высоте 0).
Дано независимых вопросов. В вопросе задана длина ног . Взойти на ступень можно только если длина ног не меньше прироста высоты именно этой ступени: . Подниматься можно только последовательно, начиная с первой ступени — как только встречается ступень с приростом больше , подъём останавливается. Нужно для каждого вопроса вывести максимальную высоту, которой можно достичь.
Ограничения: количество тестов . В каждом тесте (суммарно по всем тестам и не превышают каждое). , . Ответ может не поместиться в 32-битный тип — нужен 64-битный (long long).
Формат ввода: первая строка — . Для каждого теста: строка с и ; строка с значениями ; строка с значениями .
Формат вывода: для каждого теста — строка из чисел, ответ на каждый вопрос.
Разберём на примере
Первый тест: , вопросы .
Обратите внимание: массив приростов не отсортирован ( — не по возрастанию). Наивная мысль «раз ноги длиной , поднимемся, пока прирост ступени » работает, но подниматься нужно последовательно с первой ступени, а не выбирая ступени в удобном порядке.
- : ступень 1 — прирост 1, ОК (высота 1). Ступень 2 — прирост 2 > 1, стоп. Ответ — 1.
- или : ступени 1, 2, 3 — приросты , все , ОК. Ступень 4 — прирост 5 > 4, стоп. Высота 4.
- или : все приросты . Высота 9.
Появляется наблюдение: чтобы дойти до ступени , нужно, чтобы ВСЕ приросты были , а не только . Иначе подъём остановился бы раньше. То есть условие достижимости ступени — это max(a_1, ..., a_j) <= k.
Идея решения
Ключевое наблюдение: обозначим — префиксный максимум. Эта последовательность всегда неубывающая, независимо от того, как выглядит исходный массив . Ступень достижима при длине ног тогда и только тогда, когда .
Раз неубывает, множество достижимых индексов — это всегда префикс для наибольшего , при котором (или , если даже ). А раз отсортирован (неубывает) — искать такое можно бинарным поиском за на запрос, вместо перебора ступеней для каждого вопроса.
Ответ на вопрос — это префиксная сумма (или 0, если ).
Почему это корректно (доказательство): если ступень достижима, то по определению подъёма все ступени пройдены без остановки, то есть — а значит и их максимум . Обратное тоже верно: если , то каждый отдельный для , значит ни на одной из ступеней подъём не прервётся. Индукция по замыкает доказательство: достижимость ступеней — это ровно префикс, определяемый условием на префиксный максимум.
Строим два вспомогательных массива за один проход — (префиксный максимум) и (префиксная сумма) — а дальше каждый запрос обрабатывается независимым бинарным поиском по .
Псевдокод
для каждого теста:
прочитать n, q, массив a[1..n], массив k[1..q]
M[0] = -бесконечность (или 0, т.к. a_i >= 1)
S[0] = 0
для i от 1 до n:
M[i] = max(M[i-1], a[i])
S[i] = S[i-1] + a[i]
для каждого запроса k_i:
m = наибольший индекс j такой, что M[j] <= k_i
(бинарный поиск upper_bound по неубывающему массиву M)
если такого j нет:
ответ = 0
иначе:
ответ = S[m]
вывести ответ
Код решения
Комментарии по реализации. Оба варианта строят два вспомогательных массива за один линейный проход, а дальше каждый запрос обрабатывается независимым бинарным поиском: Python — bisect_right из стандартной библиотеки на списке prefix_max; C++ — std::upper_bound на vector<long long>. В обоих случаях bisect_right/upper_bound возвращают индекс первого элемента, который строго больше искомого значения — то есть количество элементов , что совпадает с наибольшим достижимым напрямую, без ручной коррекции границ.
Проверка на примерах
Тест 1: → , .
| элементов () | ответ | ожидание | |
|---|---|---|---|
| 1 | 1 | 1 | |
| 2 | 3 | 4 | |
| 4 | 3 | 4 | |
| 9 | 4 | 9 | |
| 10 | 4 | 9 |
Совпадает: 1 4 4 9 9.
Тест 2: → , .
| ответ | ожидание | ||
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 1 | 2 | 2 |
Совпадает: 0 2.
Тест 3: → , .
При : , ответ . Совпадает с ожиданием.
Все три теста сошлись.
Крайние случаи
- . Поскольку по ограничениям , ни одна ступень не достижима — ответ всегда 0.
- Все приросты равны и равны . Достижимы все ступени, ответ — сумма всего массива.
- Массив не отсортирован произвольно (как в примере): алгоритм не требует сортировки исходного — работает именно предвычисленный префиксный максимум.
- Большая сумма. При у верхней границы сумма высоты может достигать — не помещается в 32-битный
int, нуженlong long/64-битный тип.
Типичные ошибки
intвместоlong longв C++. Суммы до переполняют 32-битныйint.- Попытка бинарного поиска прямо по исходному массиву , который не отсортирован — работает только по префиксному максимуму .
bisect_left/lower_boundвместоbisect_right/upper_bound. Первый вариант ищет первый элемент , что даёт не то количество при повторяющихся значениях в — нужен именно вариант «строго больше», считающий все элементы .- Забыть обработать случай (ни одна ступень не достижима) — индексация
S[m-1]приm=0уйдёт в отрицательный индекс или undefined behaviour. - Считать вопросы не независимо — условие в статье явно требует отвечать на каждый вопрос отдельно, без накопления состояния между вопросами одного теста.
Сложность
Время: на тест (линейный проход для построения , и бинарных поисков). Память: .
Задача 2 — CF ~1300 — «T-primes»
Что дано
Простое число — число ровно с двумя различными положительными делителями. По аналогии определим T-простое число — число ровно с тремя различными положительными делителями.
Дан массив из чисел . Для каждого нужно определить, является ли оно T-простым.
Ограничения: , .
Формат ввода: первая строка — ; вторая строка — чисел .
Формат вывода: строк: YES, если число T-простое, иначе NO.
Разберём на примере
Дан массив .
- : делители — ровно три. YES.
- : делители — два (обычное простое). NO.
- : делители — четыре. NO.
Замечаем: единственное число из трёх, у которого ровно три делителя, — это , квадрат простого числа .
Идея решения
Утверждение: число имеет ровно три различных делителя тогда и только тогда, когда для некоторого простого .
Доказательство. В одну сторону: если ( — простое), делители — это , и все три различны (так как ). Значит, ровно три делителя.
В обратную сторону: делители числа обычно разбиваются на пары , кроме случая , когда пара «схлопывается» в один делитель. Нечётное количество делителей (три — нечётно) возможно только если — точный квадрат, . Далее раскладываем на простые множители: если , то , и число делителей равно — произведение нечётных чисел. Это произведение равно ровно 3 только когда в произведении один множитель равен 3 (то есть один простой множитель с ), а остальных множителей нет вовсе (иначе произведение было бы больше 3, так как каждый множитель , а при ). Значит, состоит из ровно одного простого множителя в первой степени, то есть — само простое число, и .
Алгоритм: для каждого проверить, является ли оно точным квадратом , и если да — проверить, простое ли . Поскольку , то . Значит, достаточно построить решето Эратосфена для чисел до один раз — и дальше каждая проверка простоты занимает .
Псевдокод
MAXR = 10^6
построить решето Эратосфена is_prime[0..MAXR]
(is_prime[0] = is_prime[1] = ложь, дальше стандартное решето)
прочитать n, массив x[1..n]
для каждого x_i:
s = целочисленный корень из x_i (округление вниз, точное)
если s * s == x_i и is_prime[s]:
вывести YES
иначе:
вывести NO
Код решения
Комментарии по реализации. Python — math.isqrt считает целочисленный корень точно, без ошибок округления floating point, поэтому дополнительная коррекция не нужна. C++ — sqrtl на long double для чисел до обычно даёт верный результат, но у плавающей точки на границах точных квадратов возможна погрешность в 1, поэтому добавлена явная коррекция s в обе стороны перед сравнением s * s == x. Решето строится один раз до , а не до — этого достаточно, так как .
Проверка на примерах
| (целое) | ? | is_prime[s]? | Ответ | Ожидание | |
|---|---|---|---|---|---|
| 4 | 2 | да | да (2 — простое) | YES | YES |
| 5 | 2 | нет () | — | NO | NO |
| 6 | 2 | нет () | — | NO | NO |
Совпадает.
Крайние случаи
- . , — точный квадрат, но не является простым числом (у него всего один делитель) — решето должно явно пометить
is_prime[1] = 0. Ответ — NO. - Точный квадрат составного числа. Например, — квадрат, но не простое → NO. Важно не путать « — точный квадрат» с « — T-простое»: нужны оба условия.
- Верхняя граница . Если , то — граничное значение решета (включительно).
- Простое число рядом с границей решета, например (простое, близкое к ): — должно дать YES.
Типичные ошибки
intвместоlong long/64-бит для . Значения до не помещаются в 32-битныйint(максимум около ).- Погрешность плавающей точки при извлечении корня.
int(sqrt(x))в языках без встроенного целочисленного корня может дать результат на 1 меньше или больше истинного из-за округления — обязательна коррекция (или использование точной целочисленной функции вродеmath.isqrt). - Решето построено только до , а не до — забыли, что решето нужно строить до границы корня, а не до границы исходных чисел.
is_prime[1]не помечен как «не простое» — по умолчанию некоторые массивы инициализируются единицами, и без явногоis_prime[1]=0число 1 ошибочно посчитается простым.- Путаница «точный квадрат» и «T-простое». Проверка только
s*s==xбез проверки простотыsдаёт неверный ответ на составных квадратах вроде 36, 100, 144.
Сложность
Построение решета: , один раз. Обработка каждого числа: после решета (плюс на извлечение корня). Итого: .
Самостоятельная тренировка
- Codeforces 1354B «Ternary String» (~CF 1200) — дана строка из символов
1,2,3; нужно найти длину кратчайшей непрерывной подстроки, содержащей все три символа хотя бы по разу (или вывести 0, если такой подстроки нет). - Codeforces 459B «Pashmak and Flowers» (~CF 1300) — дан массив красоты цветков; нужно найти максимально возможную разницу красоты между двумя цветками и количество способов выбрать такую пару.
- Codeforces 492B «Vanya and Lanterns» (~CF 1200) — дан набор фонарей на улице длины ; нужно найти минимальный радиус освещения, при котором вся улица оказывается покрыта светом.
- Codeforces 123A «Prime Permutation» (~CF 1300) — по строке из строчных латинских букв определить, можно ли переставить символы так, чтобы для каждого простого числа все позиции, кратные , содержали одинаковый символ.
Прорешай их самостоятельно — если застрянешь, разбери с AI-партнёром на codepal.ru: он не даёт готовый ответ, а подводит к решению вопросами.
Что дальше
На следующей тренировке — последняя сессия «разогрева»: сортировка в связке с бинарным поиском и хэш-таблица, снова около CF ~1300, а после неё — переход на «плато» с более длинными и составными задачами.