← Вернуться в блог

Two pointers и подсчёт допустимых наборов — задача «Close Tuples» РАЗБОР

  • 1700
  • two-pointers
  • sortings
  • combinatorics
  • binomial
  • modular-arithmetic
  • cf-1700

Что дано

Дан массив целых чисел a1,a2,,ana_1, a_2, \ldots, a_n и два числа mm и kk. Нужно найти количество наборов из mm индексов i1<i2<<imi_1 < i_2 < \cdots < i_m таких, что среди значений ai1,ai2,,aima_{i_1}, a_{i_2}, \ldots, a_{i_m} разница максимального и минимального не превышает kk:

max(ai1,,aim)min(ai1,,aim)k.\max(a_{i_1}, \ldots, a_{i_m}) - \min(a_{i_1}, \ldots, a_{i_m}) \le k.

Ответ нужно вернуть по модулю 109+710^9 + 7.

Ограничения

  • 1n21051 \le n \le 2 \cdot 10^5
  • 1m1001 \le m \le 100
  • 0k1090 \le k \le 10^9
  • 1ai1091 \le a_i \le 10^9
  • Сумма nn по всем тестам в одном входе — до 21052 \cdot 10^5.
  • Время: 3 сек, память: 256 МБ.

Формат ввода

В первой строке — число тестов TT. Для каждого теста: в первой строке nn, mm, kk; во второй — nn чисел a1,,ana_1, \ldots, a_n.

Формат вывода

Для каждого теста — одно число: количество наборов по модулю 109+710^9 + 7.

Пример 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, m=3m = 3, k=2k = 2. Отсортируем: 1 2 3 4. Нужно выбрать 3 элемента, у которых разница макс и мин 2\le 2.

Прямой перебор (43)=4\binom{4}{3} = 4 варианта:

  • {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.

Заметим важное: сортировка не меняет ответ, потому что набор — это набор значений, а не порядок. Мы считаем количество троек значений (с учётом возможных совпадений). Удобно работать с отсортированным массивом.


Идея решения

После сортировки набор индексов превращается в отрезок отсортированного массива (с подмножеством элементов внутри). Зафиксируем минимум: это какой-то элемент aia_i (после сортировки). Чтобы все элементы набора попадали в окно [ai,ai+k][a_i, a_i + k], нужно знать до какого индекса rr значение массива ещё ai+k\le a_i + k.

После сортировки массив неубывающий, поэтому для растущего ii граница rr тоже не уменьшается. Это монотонность, на которой работает шаблон two pointers: поддерживаем правый указатель rr, сдвигаем его, пока arai+ka_r \le a_i + k, и переходим к следующему ii — суммарная работа O(n)O(n).

Зафиксировав aia_i как минимум, остальные m1m - 1 элементов набора берём из «правого окна» [i+1,r][i + 1, r]. Длина окна — rir - i элементов. Количество способов выбрать m1m - 1 из rir - i — биномиальный коэффициент (rim1)\binom{r - i}{m - 1} (если rim1r - i \ge m - 1, иначе 0).

Суммарный ответ:

i=1n(r(i)im1).\sum_{i=1}^{n} \binom{r(i) - i}{m - 1}.

Биномиальные коэффициенты считаются по модулю простого p=109+7p = 10^9 + 7 через предвычисленные факториалы и обратные к ним. Поскольку rin12105r - i \le n - 1 \le 2 \cdot 10^5, достаточно факториалов до nn.

Почему фиксация минимума корректна

Каждый набор {ai1,,aim}\{a_{i_1}, \ldots, a_{i_m}\} имеет ровно один минимум (по индексу — самый левый среди равных). Когда мы перебираем ii от 11 до nn и принимаем aia_i за минимум, мы пересчитываем каждый набор ровно один раз — в тот момент, когда ii совпадает с минимальным индексом набора.

Тонкий момент: если несколько элементов массива равны, какой считать «минимумом»? После сортировки одинаковые элементы стоят рядом, и в качестве минимума мы берём самый левый среди них — индекс по отсортированному массиву. Остальные равные значения уходят в «правое окно» и учитываются как обычные кандидаты.


Решение: псевдокод

отсортировать 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

Сложность: O(nlogn)O(n \log n) на сортировку, O(n)O(n) на проход двумя указателями, O(n)O(n) на предвычисление факториалов. Итого O(nlogn)O(n \log n) на тест.


Код решения

Биномиальные коэффициенты по модулю — через факториалы и обратные. Обратное к xx по модулю простого pp ищется как xp2x^{p - 2} (малая теорема Ферма): 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 обратным проходом — это даёт O(n)O(n) на всю таблицу.

Комментарии по реализации.

  • Сдвиг правого указателя. Условие r < i + 1 нужно, чтобы при переходе к новому ii окно не «отставало»: rr должен быть как минимум i+1i + 1 (минимум aia_i занят, остальные кандидаты — справа от него).
  • Тип под araia_r - a_i. Значения aia_i до 10910^9, kk до 10910^9, разница умещается в int32, но для совместимости с возможным расширением условий безопаснее long long в C++.
  • Размер таблицы факториалов. Берём n2105n \le 2 \cdot 10^5 — максимум по условию. Считать заранее, до цикла по тестам: иначе на T=105T = 10^5 тестах потеряем время на переинициализацию.
  • 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).

  • i=0i = 0, ai=1a_i = 1. Двигаем rr, пока ar12a_r - 1 \le 2: r=0123r = 0 \to 1 \to 2 \to 3 (при r=3r = 3, a3=4a_3 = 4, 41=3>24 - 1 = 3 > 2, стоп). Конечный r=3r = 3. Window = 301=23 - 0 - 1 = 2. (22)=1\binom{2}{2} = 1.
  • i=1i = 1, ai=2a_i = 2. r=3r = 3, a3=4a_3 = 4, 42=224 - 2 = 2 \le 2, r4r \to 4. Window = 411=24 - 1 - 1 = 2. (22)=1\binom{2}{2} = 1.
  • i=2i = 2, ai=3a_i = 3. r=4r = 4. Window = 421=14 - 2 - 1 = 1. (12)=0\binom{1}{2} = 0.
  • i=3i = 3, ai=4a_i = 4. r=4r = 4. Window = 431=04 - 3 - 1 = 0. (02)=0\binom{0}{2} = 0.

Сумма: 1+1+0+0=21 + 1 + 0 + 0 = 2. Ответ: 2

Пример 2: n=4, m=2, k=1, массив 1 1 1 1

После сортировки: 1 1 1 1.

  • i=0i = 0: rr растёт до 4 (все элементы 1+1\le 1 + 1). Window = 401=34 - 0 - 1 = 3. (31)=3\binom{3}{1} = 3.
  • i=1i = 1: Window = 411=24 - 1 - 1 = 2. (21)=2\binom{2}{1} = 2.
  • i=2i = 2: Window = 421=14 - 2 - 1 = 1. (11)=1\binom{1}{1} = 1.
  • i=3i = 3: Window = 00. (01)=0\binom{0}{1} = 0.

Сумма: 3+2+1+0=63 + 2 + 1 + 0 = 6. Ответ: 6

Пример 3: n=1, m=1, k=1, массив 1

  • i=0i = 0: r=1r = 1. Window = 00. (00)=1\binom{0}{0} = 1.

Ответ: 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. k=0k = 0 означает, что все элементы набора должны быть равны.

Группы одинаковых: три 1 (индексы 0..2), три 2 (3..5), две 3 (6..7), две 4 (8..9).

  • Для каждой 1: правое окно — только индексы с тем же значением 1.
    • i=0i = 0: окно длиной 2 (индексы 1, 2). (23)=0\binom{2}{3} = 0.
    • i=1i = 1: окно 1. (13)=0\binom{1}{3} = 0.
    • i=2i = 2: окно 0. (03)=0\binom{0}{3} = 0.
  • Аналогично для 2, 3, 4.

Никакая группа из четырёх одинаковых элементов в массиве не существует → ответ 0. Ответ: 0


Крайние случаи, на которых можно споткнуться

1. m=1m = 1

Любой одиночный элемент — допустимый набор. Окно длиной 0, и (00)=1\binom{0}{0} = 1 для каждого ii. Ответ =n= n. Проверяется на примере 3.

2. k=0k = 0 и нет повторов

Ответ 0 при m2m \ge 2. Алгоритм автоматически выдаст 0: rr остановится сразу после ii, окно будет 0.

3. Все элементы равны

Тогда r=nr = n для каждого ii, окно = ni1n - i - 1. Сумма (ni1m1)\binom{n - i - 1}{m - 1} по i=0,,n1i = 0, \ldots, n - 1 по формуле хоккейной клюшки даёт (nm)\binom{n}{m} — что и ожидаемо: любой набор из mm индексов подходит.

4. m>nm > n

Тогда m1>n1m - 1 > n - 1 \ge окна. (windowm1)=0\binom{\text{window}}{m - 1} = 0 всегда. Ответ 0.

5. Большие kk, в окне весь массив

Если kmaxaminak \ge \max a - \min a, то r=nr = n всегда. Ответ =i=0n1(ni1m1)=(nm)= \sum_{i = 0}^{n - 1} \binom{n - i - 1}{m - 1} = \binom{n}{m}.


Типичные ошибки

  1. Двигают rr заново для каждого ii. Получается O(n2)O(n^2) вместо O(n)O(n). Лекарство — указатель rr хранится между итерациями внешнего цикла, монотонность массива гарантирует, что он только растёт.
  2. Считают (rim)\binom{r - i}{m} вместо (ri1m1)\binom{r - i - 1}{m - 1}. Путают: «всего элементов в окне rir - i» против «остальных, кроме фиксированного минимума, ri1r - i - 1, и из них выбираем m1m - 1».
  3. Забывают по модулю в power. Если aa или промежуточное произведение превысят 2632^{63} в C++ — переполнение. Каждое умножение должно сопровождаться % MOD.
  4. Не предвычислили факториалы. Считать (nk)\binom{n}{k} через формулу с делениями на каждом шаге — медленно и небезопасно по модулю (деление нужно как умножение на обратное). Таблица факториалов и обратных — линейная.
  5. Сортировка стабильная не нужна. В Python sorted стабильная, в C++ sort нет, но это не влияет на результат: мы оперируем значениями, а не парами (значение, исходный индекс).
  6. Off-by-one в условии цикла while. a[r] - a[i] <= k — нестрогое неравенство, потому что разница ровно kk ещё допустима. Сильное неравенство < уберёт допустимые наборы и даст WA на тестах с граничными kk.

Анализ сложности

  • Время: O((n+MAX_N)+nlogn)O((n + \text{MAX\_N}) + n \log n) на тест, где предвычисление таблиц — общее для всего входа. На сумму n2105\sum n \le 2 \cdot 10^5 — суммарно укладываемся в 3 сек.
  • Память: O(MAX_N)=O(2105)O(\text{MAX\_N}) = O(2 \cdot 10^5) под факториалы + O(n)O(n) под массив.

Что ещё полезно потренировать

  • Codeforces 1029C «Maximal Intersection» (~1500) — two pointers по отсортированным концам отрезков, классическое введение в шаблон.
  • Codeforces 279B «Books» (~1400) — окно, в котором сумма не превосходит ограничения. Хороший разогрев на двух указателях.
  • Codeforces 1234B2 «Social Network (hard version)» (~1400) — другой способ работы со «скользящим окном» через структуру данных.
  • acmp.ru задача 358 «Сумма цифр» — не на two pointers, но классическая отработка предвычислений.

Итого

  • Идея: сортируем массив, двумя указателями находим для каждого минимума максимальный конец «окна разницы», считаем (ri1m1)\binom{r - i - 1}{m - 1}.
  • Сложность: O(nlogn)O(n \log n) на сортировку + O(n)O(n) на проход указателями + O(MAX_N)O(\text{MAX\_N}) на предвычисление.
  • Ловушки: сброс rr между итерациями (потеря O(n)O(n)O(n2)O(n^2)), путаница «(nk)\binom{n}{k} vs (n1k1)\binom{n - 1}{k - 1}» при фиксации минимума, забытое % MOD в умножениях.

Попробуй разобрать похожие задачи

В CodePal AI-партнёр подсказывает идею, а не ответ. Разбор в диалоге, код проверяется в браузере.