Что дано
Дан массив целых чисел и два числа и . Нужно найти количество наборов из индексов таких, что среди значений разница максимального и минимального не превышает :
Ответ нужно вернуть по модулю .
Ограничения
- Сумма по всем тестам в одном входе — до .
- Время: 3 сек, память: 256 МБ.
Формат ввода
В первой строке — число тестов . Для каждого теста: в первой строке , , ; во второй — чисел .
Формат вывода
Для каждого теста — одно число: количество наборов по модулю .
Пример 1
Вход:
4
4 3 2
1 2 4 3
4 2 1
1 1 1 1
1 1 1
1
10 4 0
1 3 1 2 2 3 4 4 1 2
Вывод:
2
6
1
0
Разберём первый пример руками
Массив 1 2 4 3, , . Отсортируем: 1 2 3 4. Нужно выбрать 3 элемента, у которых разница макс и мин .
Прямой перебор варианта:
{1, 2, 3}→ 3 - 1 = 2 ✓{1, 2, 4}→ 4 - 1 = 3 ✗{1, 3, 4}→ 4 - 1 = 3 ✗{2, 3, 4}→ 4 - 2 = 2 ✓
Ответ: 2.
Заметим важное: сортировка не меняет ответ, потому что набор — это набор значений, а не порядок. Мы считаем количество троек значений (с учётом возможных совпадений). Удобно работать с отсортированным массивом.
Идея решения
После сортировки набор индексов превращается в отрезок отсортированного массива (с подмножеством элементов внутри). Зафиксируем минимум: это какой-то элемент (после сортировки). Чтобы все элементы набора попадали в окно , нужно знать до какого индекса значение массива ещё .
После сортировки массив неубывающий, поэтому для растущего граница тоже не уменьшается. Это монотонность, на которой работает шаблон two pointers: поддерживаем правый указатель , сдвигаем его, пока , и переходим к следующему — суммарная работа .
Зафиксировав как минимум, остальные элементов набора берём из «правого окна» . Длина окна — элементов. Количество способов выбрать из — биномиальный коэффициент (если , иначе 0).
Суммарный ответ:
Биномиальные коэффициенты считаются по модулю простого через предвычисленные факториалы и обратные к ним. Поскольку , достаточно факториалов до .
Почему фиксация минимума корректна
Каждый набор имеет ровно один минимум (по индексу — самый левый среди равных). Когда мы перебираем от до и принимаем за минимум, мы пересчитываем каждый набор ровно один раз — в тот момент, когда совпадает с минимальным индексом набора.
Тонкий момент: если несколько элементов массива равны, какой считать «минимумом»? После сортировки одинаковые элементы стоят рядом, и в качестве минимума мы берём самый левый среди них — индекс по отсортированному массиву. Остальные равные значения уходят в «правое окно» и учитываются как обычные кандидаты.
Решение: псевдокод
отсортировать a по неубыванию
предвычислить fact[0..n], inv_fact[0..n] по модулю p
r ← 0
ans ← 0
для i от 0 до n - 1:
пока r < n и a[r] - a[i] ≤ k:
r ← r + 1
# окно — индексы [i, r), длина r - i
# фиксируем a[i] как минимум, ещё m - 1 выбираем из r - i - 1 правых соседей
window ← r - i - 1
если window ≥ m - 1:
ans ← (ans + C(window, m - 1)) mod p
вернуть ans
Сложность: на сортировку, на проход двумя указателями, на предвычисление факториалов. Итого на тест.
Код решения
Биномиальные коэффициенты по модулю — через факториалы и обратные. Обратное к по модулю простого ищется как (малая теорема Ферма): pow(x, p - 2, p) в Python и power(x, p - 2) в C++. Считаем массив fact итеративно, inv_fact[n] — один раз через возведение в степень, остальные inv_fact[i] = inv_fact[i + 1] * (i + 1) mod p обратным проходом — это даёт на всю таблицу.
Комментарии по реализации.
- Сдвиг правого указателя. Условие
r < i + 1нужно, чтобы при переходе к новому окно не «отставало»: должен быть как минимум (минимум занят, остальные кандидаты — справа от него). - Тип под . Значения до , до , разница умещается в
int32, но для совместимости с возможным расширением условий безопаснееlong longв C++. - Размер таблицы факториалов. Берём — максимум по условию. Считать заранее, до цикла по тестам: иначе на тестах потеряем время на переинициализацию.
binom(window, m - 1)приwindow < m - 1возвращает 0 — это корректно: значит, в окне меньше элементов, чем нужно для набора.
Проверим на всех примерах из условия
Пример 1: n=4, m=3, k=2, массив 1 2 4 3
После сортировки: 1 2 3 4 (индексы 0..3).
- , . Двигаем , пока : (при , , , стоп). Конечный . Window = . .
- , . , , , . Window = . .
- , . . Window = . .
- , . . Window = . .
Сумма: . Ответ: 2 ✓
Пример 2: n=4, m=2, k=1, массив 1 1 1 1
После сортировки: 1 1 1 1.
- : растёт до 4 (все элементы ). Window = . .
- : Window = . .
- : Window = . .
- : Window = . .
Сумма: . Ответ: 6 ✓
Пример 3: n=1, m=1, k=1, массив 1
- : . Window = . .
Ответ: 1 ✓
Пример 4: n=10, m=4, k=0, массив 1 3 1 2 2 3 4 4 1 2
После сортировки: 1 1 1 2 2 2 3 3 4 4. означает, что все элементы набора должны быть равны.
Группы одинаковых: три 1 (индексы 0..2), три 2 (3..5), две 3 (6..7), две 4 (8..9).
- Для каждой
1: правое окно — только индексы с тем же значением1.- : окно длиной 2 (индексы 1, 2). .
- : окно 1. .
- : окно 0. .
- Аналогично для
2,3,4.
Никакая группа из четырёх одинаковых элементов в массиве не существует → ответ 0. Ответ: 0 ✓
Крайние случаи, на которых можно споткнуться
1.
Любой одиночный элемент — допустимый набор. Окно длиной 0, и для каждого . Ответ . Проверяется на примере 3.
2. и нет повторов
Ответ 0 при . Алгоритм автоматически выдаст 0: остановится сразу после , окно будет 0.
3. Все элементы равны
Тогда для каждого , окно = . Сумма по по формуле хоккейной клюшки даёт — что и ожидаемо: любой набор из индексов подходит.
4.
Тогда окна. всегда. Ответ 0.
5. Большие , в окне весь массив
Если , то всегда. Ответ .
Типичные ошибки
- Двигают заново для каждого . Получается вместо . Лекарство — указатель хранится между итерациями внешнего цикла, монотонность массива гарантирует, что он только растёт.
- Считают вместо . Путают: «всего элементов в окне » против «остальных, кроме фиксированного минимума, , и из них выбираем ».
- Забывают по модулю в
power. Если или промежуточное произведение превысят в C++ — переполнение. Каждое умножение должно сопровождаться% MOD. - Не предвычислили факториалы. Считать через формулу с делениями на каждом шаге — медленно и небезопасно по модулю (деление нужно как умножение на обратное). Таблица факториалов и обратных — линейная.
- Сортировка стабильная не нужна. В Python
sortedстабильная, в C++sortнет, но это не влияет на результат: мы оперируем значениями, а не парами (значение, исходный индекс). - Off-by-one в условии цикла
while.a[r] - a[i] <= k— нестрогое неравенство, потому что разница ровно ещё допустима. Сильное неравенство<уберёт допустимые наборы и даст WA на тестах с граничными .
Анализ сложности
- Время: на тест, где предвычисление таблиц — общее для всего входа. На сумму — суммарно укладываемся в 3 сек.
- Память: под факториалы + под массив.
Что ещё полезно потренировать
- Codeforces 1029C «Maximal Intersection» (~1500) — two pointers по отсортированным концам отрезков, классическое введение в шаблон.
- Codeforces 279B «Books» (~1400) — окно, в котором сумма не превосходит ограничения. Хороший разогрев на двух указателях.
- Codeforces 1234B2 «Social Network (hard version)» (~1400) — другой способ работы со «скользящим окном» через структуру данных.
- acmp.ru задача 358 «Сумма цифр» — не на two pointers, но классическая отработка предвычислений.
Итого
- Идея: сортируем массив, двумя указателями находим для каждого минимума максимальный конец «окна разницы», считаем .
- Сложность: на сортировку + на проход указателями + на предвычисление.
- Ловушки: сброс между итерациями (потеря → ), путаница « vs » при фиксации минимума, забытое
% MODв умножениях.