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

Стратегия победы: префиксные суммы — от 1D к 2D и подсчёту на подотрезках СТРАТЕГИЯ

  • prefix-sums
  • 2d-prefix-sums
  • hashmap
  • trayektoriya
  • cf
  • inclusion-exclusion

О чём эта статья

Серия «Стратегия победы» — про траекторию тренировки, а не про одну задачу. Берём одну технику и проходим её на трёх уровнях нарастающей сложности, явно фиксируя, что именно усложняется между ступенями. Тема этой статьи — префиксные суммы, одна из самых дешёвых по реализации и самых частых на полосах CF 1100–2000.

Большинство участников осваивает базовый шаблон префиксных сумм быстро — за пару задач. Сложнее становится, когда задача требует двумерных префиксов (с inclusion-exclusion) или когда префиксы становятся не самоцелью, а инструментом подсчёта — как в задачах «сколько подотрезков обладают свойством XX». Эта статья показывает путь от базового шаблона к этому уровню применения, шаг за шагом.


Ядро техники

Префиксная сумма — это вспомогательный массив, в котором каждый элемент хранит сумму всех элементов исходного массива от начала до текущей позиции. Имея такой массив, ответ на любой запрос «сумма на отрезке [l,r][l, r]» считается за одно вычитание: P[r+1]P[l]P[r + 1] - P[l]. Препроцессинг — O(n)O(n), каждый запрос — O(1)O(1).

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

Базовый шаблон (псевдокод)

прочитать массив a длины n
P[0] ← 0
для i от 0 до n-1:
    P[i+1] ← P[i] + a[i]

на каждый запрос (l, r):     // a[l..r] включительно
    ответ ← P[r+1] - P[l]

Сложность базового шаблона: O(n+q)O(n + q) время, O(n)O(n) память.

Что мы будем усложнять по мере роста:

  • Ступень 1 → ступень 2: одномерный массив превращается в двумерную сетку; одно вычитание заменяется на четыре с учётом формулы включений-исключений.
  • Ступень 2 → ступень 3: вместо «считать сумму на отрезке» приходит обратная задача: «по какому свойству префиксной суммы найти пары (l,r)(l, r), чтобы отрезок имел нужное свойство». Здесь префикс соединяется с хешмапом.

Это и есть смысл серии: не «три разные задачи на префиксы», а осознанный путь от прямого применения шаблона к его роли как кирпичика более сложных решений.


Ступень 1 — CF ~1200 (одномерный шаблон, запросы на отрезке)

Условие

Дан массив a1,a2,,ana_1, a_2, \ldots, a_n из целых чисел. Поступает qq запросов; каждый запрос — пара (l,r)(l, r), 1lrn1 \le l \le r \le n. На каждый запрос нужно вывести сумму al+al+1++ara_l + a_{l+1} + \cdots + a_r.

Ограничения. 1n,q1051 \le n, q \le 10^5, ai109|a_i| \le 10^9.

Как применяется базовый шаблон

Лобовое решение — для каждого запроса пройти от ll до rr и просуммировать. Это O(nq)=1010O(nq) = 10^{10} операций. Не помещается.

Заведём префиксный массив PP длины n+1n + 1 с конвенцией P[0]=0P[0] = 0, P[i]=a1+a2++aiP[i] = a_1 + a_2 + \cdots + a_i. Тогда сумма на отрезке [l,r][l, r] — это P[r]P[l1]P[r] - P[l - 1] (если индексация массива aa с единицы, как в условии). Препроцессинг — O(n)O(n), каждый запрос — O(1)O(1).

Разбор на примере

n=5n = 5, a=[3,1,4,1,5]a = [3, -1, 4, 1, 5]. Префиксы:

ii001122334455
P[i]P[i]00332266771212

Запросы:

  • (2,4)(2, 4): P[4]P[1]=73=4P[4] - P[1] = 7 - 3 = 4. Проверка: 1+4+1=4-1 + 4 + 1 = 4
  • (1,5)(1, 5): P[5]P[0]=120=12P[5] - P[0] = 12 - 0 = 12. Проверка: 31+4+1+5=123 - 1 + 4 + 1 + 5 = 12
  • (3,3)(3, 3): P[3]P[2]=62=4P[3] - P[2] = 6 - 2 = 4. Проверка: a3=4a_3 = 4

Код решения

Сложность: O(n+q)O(n + q).

Что закрыли

На этом уровне закрыли сам шаблон 1D-префиксов: индексацию с единицы или нуля, базовую формулу P[r]P[l1]P[r] - P[l - 1]. На следующей ступени к одной оси добавится вторая, и одно вычитание расщепится на четыре.


Ступень 2 — CF ~1600 (двумерные префиксы и inclusion-exclusion)

Условие

Дана прямоугольная сетка n×mn \times m с целыми числами ai,ja_{i, j}. Поступает qq запросов; каждый запрос — четвёрка (r1,c1,r2,c2)(r_1, c_1, r_2, c_2), 1r1r2n1 \le r_1 \le r_2 \le n, 1c1c2m1 \le c_1 \le c_2 \le m. На каждый запрос нужно вывести сумму чисел в подпрямоугольнике с верхним левым углом (r1,c1)(r_1, c_1) и нижним правым (r2,c2)(r_2, c_2) включительно.

Ограничения. 1n,m10001 \le n, m \le 1000, 1q1051 \le q \le 10^5, ai,j109|a_{i, j}| \le 10^9.

Что меняется в шаблоне

  • Состояние: было одно — индекс ii. Стало два — пара (i,j)(i, j).
  • Препроцессинг: было P[i+1]=P[i]+aiP[i + 1] = P[i] + a_i. Стало P[i][j]=P[i1][j]+P[i][j1]P[i1][j1]+a[i][j].P[i][j] = P[i - 1][j] + P[i][j - 1] - P[i - 1][j - 1] + a[i][j]. Формула — прямое применение принципа включений-исключений: «прямоугольник от (0,0)(0, 0) до (i,j)(i, j) = верхняя полоса + левая полоса − дважды посчитанный квадрат + текущая клетка».
  • Запрос: было P[r]P[l1]P[r] - P[l - 1]. Стало ответ=P[r2][c2]P[r11][c2]P[r2][c11]+P[r11][c11].\text{ответ} = P[r_2][c_2] - P[r_1 - 1][c_2] - P[r_2][c_1 - 1] + P[r_1 - 1][c_1 - 1]. Четыре слагаемых с чередованием знаков — те же включения-исключения, но в обратном направлении.

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

Разбор на примере

Сетка 3×33 \times 3:

1 2 3
4 5 6
7 8 9

Префиксы P[i][j]P[i][j]P[0][]=P[][0]=0P[0][\cdot] = P[\cdot][0] = 0):

i\ji \backslash j00112233
0000000000
1100113366
22005512122121
3300121227274545

Запрос (2,2,3,3)(2, 2, 3, 3): подпрямоугольник со значениями 5,6,8,95, 6, 8, 9, сумма 2828.

По формуле:

P[3][3]P[1][3]P[3][1]+P[1][1]=45612+1=28.P[3][3] - P[1][3] - P[3][1] + P[1][1] = 45 - 6 - 12 + 1 = 28.

Совпадает.

Код решения

Сложность: O(nm+q)O(n \cdot m + q). Память O(nm)O(n \cdot m) — при n=m=1000n = m = 1000 это 10610^6 long long, около 88 МБ.

Что закрыли

Ступень 2 — умение расширить шаблон на kk осей. На двух осях получаем четыре слагаемых; на трёх (если когда-то понадобятся 3D-префиксы для подсчёта в объёме) — восемь, с тем же чередованием знаков. Это inclusion-exclusion в чистом виде: коэффициенты (1)S(-1)^{|S|} по подмножеству SS «выбранных осей».

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


Ступень 3 — CF ~2000 (prefix + hashmap, подсчёт подотрезков с заданной суммой)

Условие

Дан массив целых чисел a1,a2,,ana_1, a_2, \ldots, a_n и целое число KK. Найти количество подотрезков массива (непрерывных подмассивов al,al+1,,ara_l, a_{l+1}, \ldots, a_r), у которых сумма равна ровно KK.

Ограничения. 1n21051 \le n \le 2 \cdot 10^5, ai,K109|a_i|, |K| \le 10^9.

Что меняется в шаблоне

  • Структура данных: двумерного массива больше нет. Префиксы остались одномерные, но теперь они ключи хешмапа, а не индексы для прямого доступа.
  • Интеграция с другой техникой: соединяются префиксные суммы и хеш-таблица. Свойство «сумма отрезка [l,r][l, r] равна KK» переписывается как «P[r]P[l1]=KP[r] - P[l - 1] = K», то есть «пара префиксов с разностью KK». Подсчёт пар префиксов с заданной разностью — стандартная задача на хешмап: для каждого правого конца P[r]P[r] проверяем, сколько раз раньше встречалось значение P[r]KP[r] - K.
  • Доказательство: соответствие подотрезков и пар префиксов биективное. Каждый подотрезок [l,r][l, r] задаёт пару (P[l1],P[r])(P[l - 1], P[r]), и наоборот, каждая пара (P[i],P[j])(P[i], P[j]) с i<ji < j задаёт подотрезок [i+1,j][i + 1, j]. Поэтому подсчёт пар префиксов с разностью KK — это то же самое, что подсчёт подотрезков с суммой KK.

Ключевой момент — сдвиг конвенции: префикс был «суммой до позиции ii», теперь он «значение, которое мы хешируем». Содержательно — одно и то же, но взгляд меняется.

Разбор на примере

n=5n = 5, a=[2,1,3,1,1]a = [2, -1, 3, 1, -1], K=3K = 3. Префиксы (с P[0]=0P[0] = 0): 0,2,1,4,5,40, 2, 1, 4, 5, 4.

Идём слева направо. Для каждой позиции rr ищем, сколько раз раньше встречалось P[r]KP[r] - K.

rrP[r]P[r]P[r]KP[r] - KСколько раз P[r]KP[r] - K встречалось до этогоНакопленный ответ
11221-10000
22112-20000
33441111 (это P[2]P[2])11
44552211 (это P[1]P[1])22
55441111 (это P[2]P[2])33

После каждого шага увеличиваем счётчик для P[r]P[r] в хешмапе. Важно: в начале мы кладём в хешмап одну запись {0:1}\{0: 1\}, чтобы корректно обрабатывались отрезки, начинающиеся с первого элемента (когда l=1l = 1, разность с P[l1]=P[0]=0P[l - 1] = P[0] = 0).

Ответ — 33. Проверка ручным перебором подотрезков с суммой 33:

  • [2,1,3,1,1][2, -1, 3, 1, -1] — позиции 1..51..5, сумма 44. Нет.
  • [1,3,1][-1, 3, 1] — позиции 2..42..4, сумма 33. Да.
  • [3][3] — позиция 33, сумма 33. Да.
  • [3,1,1][3, 1, -1] — позиции 3..53..5, сумма 33. Да.

Три подотрезка, ответ совпадает.

Код решения

Сложность: O(n)O(n) в среднем (с учётом операций хеш-таблицы). Память: O(n)O(n) под хешмап.

Что закрыли

На этом уровне техника применяется в полную силу. Префиксные суммы стали не самоцелью, а переменной, по которой мы строим решение задачи о подотрезках. Свойство, по которому ищем подотрезки, можно поменять — и шаблон останется в силе:

  • «Сумма равна KK» → P[r]P[l1]=KP[r] - P[l - 1] = K → пары префиксов с разностью KK.
  • «Сумма делится на mm» → P[r]P[l1](modm)P[r] \equiv P[l - 1] \pmod m → пары префиксов с одинаковым остатком.
  • «Сумма K\ge K» при положительных значениях → отсортированные префиксы + бинпоиск или two pointers.
  • «XOR-сумма равна KK» → пары префиксов с XOR равным KK → хешмап по XOR-префиксам.

Следующий шаг — выход за рамки техники: задачи, где префиксная сумма — лишь один из элементов; нужны DP или структуры данных поверх неё.


Что нужно держать в голове при переходе между ступенями

СтупеньCFКлючевое состояниеКлючевое усложнение vs предыдущая
1~12001200P[i]P[i] — линейный массив сумм
2~16001600P[i][j]P[i][j] — двумерный массивinclusion-exclusion: четыре слагаемых вместо двух
3~20002000P[i]P[i] как ключ хеш-таблицыподсчёт пар префиксов с разностью KK

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


Типичные ловушки на каждой ступени

  • Ступень 1: путаница «включающий» и «полу-открытый» правый конец. В одной строке кода P[r + 1] - P[l] (правый конец включительно), в другой — P[r] - P[l] (правый конец исключительно). Симптом — off-by-one на коротких отрезках. Лекарство — одна конвенция на всю задачу.
  • Ступень 2: путаница строк и столбцов. Формула с четырьмя слагаемыми требует строгого порядка осей. Если перепутать (i1,j)(i - 1, j) и (i,j1)(i, j - 1), на симметричных примерах решение «случайно» проходит, на несимметричных — WA. Лекарство — рисовать формулу на бумаге перед написанием кода.
  • Ступень 3: забытая cnt[0] = 1. Если не учесть «пустой префикс» в начале, потеряем подотрезки, начинающиеся с первого элемента. Симптом — ответ меньше эталона ровно на число таких подотрезков. Лекарство — выработать рефлекс: первая строка после объявления хешмапа — увеличить счётчик для нулевого префикса.
  • Ступень 3: unordered_map против анти-хеш-атак. На Codeforces бывают тесты, специально сконструированные против unordered_map<long long, ...> со стандартным хешем. Лекарство — кастомный хеш с рандомным сидом или map<long long, ...> (логарифмический, иногда чуть быстрее на адверсариальных тестах).
  • Все ступени: int под суммы. n=105n = 10^5, ai=109|a_i| = 10^9 — сумма до 101410^{14}, int переполнится. long long в C++ обязателен, в Python автоматически.

План тренировки

Практический чек-лист:

  1. Решить ступень 1 самостоятельно (без подсмотра кода). Цель — отработать индексацию и формулу P[r]P[l1]P[r] - P[l - 1] до автоматизма.
  2. Решить 3355 задач полосы CF ~12001200 на одномерные префиксы. На Codeforces можно искать по тегу dp + сложности 1100110013001300, фильтровать по формулировкам «сумма на отрезке», «диапазонный запрос».
  3. Перейти к ступени 2. Если застреваем более 3030 минут — открыть разбор в этой статье. После — самостоятельно реализовать без подсмотра.
  4. После ступени 2 — 3355 задач полосы CF ~1500150016001600 на двумерные префиксы. Часто формулируются как «максимальная сумма прямоугольника» (это уже Kadane 2D, но шаг построения префиксов в нём — обязательный).
  5. Ступень 3 — аналогично, но без жёсткого ограничения по времени. Это полоса CF ~1800180020002000, разбираться имеет смысл медленно. Лучший способ закрепить — переформулировать самостоятельно несколько задач с других тегов («подсчёт подотрезков», «подсчёт пар индексов») через язык «префикс + хешмап» и убедиться, что схема ложится.
  6. Финал тренировки — попытаться решить смесь: задача, где одна часть — двумерные префиксы, другая — подсчёт через хешмап. Такие гибриды появляются в полосе CF ~22002200+ и тренируют не саму технику, а переключение между ступенями внутри одной задачи.

Итого

  • Тема: префиксные суммы — от диапазонных запросов к подсчёту подотрезков.
  • Траектория: CF ~12001200 → ~16001600 → ~20002000, с усложнением: одна ось → две оси (inclusion-exclusion) → префикс как ключ хешмапа.
  • Ключевое умение: не просто знание формулы P[r]P[l1]P[r] - P[l - 1], а способность переформулировать задачу через язык префиксов: видеть «подотрезок с суммой KK» как «пара префиксов с разностью KK», «подматрица с суммой KK» как «четыре префикса, связанные формулой включений-исключений», и так далее.

В серии: Стратегия победы

  1. 1Стратегия победы: DP — от очевидного шаблона к ёмкому состоянию
  2. 2Стратегия победы: графы — от BFS к многоходовым обходам
  3. 3Стратегия победы: бинпоиск по ответу — траектория 1200 → 1500 → 2000
  4. 4Стратегия победы: жадные — когда жадность ломается и как её спасти
  5. 5Стратегия победы: префиксные суммы — от 1D к 2D и подсчёту на подотрезках — эта статья

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

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