О чём статья
Префиксные суммы — самая дешёвая по реализации техника на полосе сложности CF 1100–1700, и одна из самых частых. Идея сводится к одному переходу: вместо того чтобы на каждый запрос «сумма на отрезке» считать операций, заранее накопить «сумму от начала до позиции » — и тогда сумма любого отрезка считается за через одно вычитание.
Эта статья — про базовый каркас и три типичных применения: одномерный шаблон, его двумерное обобщение через формулу включений-исключений, и более содержательный приём «prefix + hashmap», который даёт за ответ на «сколько подотрезков имеют заданную сумму». Цель — довести шаблон до автоматизма, чтобы при виде слов «сумма на отрезке» или «подмножество подряд идущих элементов» рука сама тянулась за префиксным массивом, а ловушки на границах не съедали часов отладки.
Когда применять: сигналы в условии
Префиксные суммы уместны, когда задача оперирует аддитивными характеристиками подсегмента (или подматрицы), и при этом массив не изменяется между запросами. Несколько узнаваемых маркеров:
- «Сумма » / «сумма в подпрямоугольнике». Прямой сигнал: одна цельная операция «суммирования по диапазону» — главный кандидат на префиксы.
- «Дано запросов вида …, выведи ответы». Если запросов много (сотни тысяч), а на каждый напрашивается — общая сложность не помещается. Префиксы превращают её в .
- «Подсчитать, сколько подотрезков обладают свойством ». Свойство «сумма равна », «сумма делится на », «сумма больше » — классические постановки под комбинацию prefix + hashmap или prefix + сортировка.
- Массив не изменяется. В условии нет «затем выполнить обновлений» или «измените на ». Если изменения есть — это сигнал к Fenwick / сегментному дереву, а не к префиксам.
- Аддитивная операция. Сумма, XOR, иногда количество чего-то конкретного. Если оператор —
min/max/gcd, шаблон префиксов даёт ответы только для «диапазонов от начала», но не для произвольных — тут уже sparse table.
Если ничего из этих сигналов нет — префиксы, скорее всего, не нужны. Если есть один — попробовать стоит. Если два-три — почти точно префиксы; остаётся только аккуратно выбрать индексацию и убедиться, что ответ помещается в выбранный целочисленный тип.
Базовый шаблон (1D)
Идея в одну фразу
Завести вспомогательный массив длины , где — сумма первых элементов исходного массива. Тогда сумма любого отрезка выражается через два значения этого массива.
Конвенция индексации
Пусть исходный массив длины , индексы . Префиксный массив длины : , дальше каждое следующее значение — это предыдущее плюс соответствующий элемент массива. То есть равно сумме первых элементов массива.
Тогда для отрезка (оба конца включительно) сумма равна . Если в условии полуоткрытый сегмент (правый конец исключительно) — формула становится . Это та же самая идея, просто без «» в индексе.
Важный совет: выбрать одну из двух конвенций (включительно или исключительно правый конец) и держаться её до конца задачи. Большая часть off-by-one в префиксных суммах рождается из перескока между этими двумя стилями в разных местах кода.
Псевдокод
прочитать массив 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]
Сложность: препроцессинг + на запрос. На запросах — итог против наивных .
Почему шаблон работает
Сумма на отрезке — это разница двух «сумм от начала»: «всё до правого конца включительно» минус «всё до левого конца исключительно». Первое — это , второе — , отсюда . Никакой «магии».
Тот же приём работает для любой операции, у которой есть обратная: XOR (обратная — снова XOR), сумма по простому модулю (обратная — вычитание по модулю). Для min / max обратной операции нет — поэтому шаблон префиксов на них не обобщается.
Задача-иллюстрация 1: суммы на отрезке (~CF 1100)
Условие
Дан массив целых чисел длины и запросов. Каждый запрос — пара , . Нужно вывести сумму .
Ограничения. , . Гарантируется, что ответ умещается в 64-битное знаковое целое.
Формат ввода. В первой строке — и . Во второй — чисел . Далее — строк по два числа .
Формат вывода. строк — ответы на запросы в порядке их следования.
Пример. , запрос . Ответ — .
Применение шаблона
База — стандартная:
(Проверка: , , , , .)
Запрос : . ✓
Код решения
Что поменялось
Ничего по сравнению с базовым шаблоном — задача один в один по идее. Это «нулевая» иллюстрация, фиксирующая шаблон в памяти. Дальше начинаются модификации.
Задача-иллюстрация 2: сумма в подпрямоугольнике (~CF 1400)
Условие
Дана сетка целых чисел , , . На каждый из запросов — четвёрка , , . Нужно вывести сумму всех элементов в подпрямоугольнике с углами и включительно.
Ограничения. , , .
Пример. Сетка :
1 2 3
4 5 6
7 8 9
Запрос : подпрямоугольник 1 2 / 4 5, сумма .
Применение шаблона
Двумерный аналог префиксного массива: — это сумма всех элементов в верхне-левом подпрямоугольнике, у которого правый нижний угол находится перед клеткой (то есть включает строки и столбцы ). Размер — ; нулевая строка и нулевой столбец заполнены нулями — это база, которая снимет с формул проверки на края.
Заполнение идёт по принципу «сложить две половины и вычесть пересечение». Чтобы посчитать (то есть сумму подпрямоугольника, включающего всё слева-сверху и плюс саму клетку ), складываем:
- сумму «верхней половины» — всё, что выше текущей строки;
- сумму «левой половины» — всё, что левее текущего столбца;
- сам элемент .
Но «верхняя половина» и «левая половина» пересекаются по верхне-левому подпрямоугольнику — его учли дважды, поэтому вычитаем один раз. В коде это одно выражение P[i+1][j+1] = P[i][j+1] + P[i+1][j] - P[i][j] + a[i][j].
Запрос «сумма в подпрямоугольнике от до включительно» — та же логика, в обратную сторону. Берём «полный подпрямоугольник от до » — это . Из него вычитаем верхнюю полосу (всё, что выше ) и левую полосу (всё, что левее ). Эти полосы пересекаются по верхне-левому подпрямоугольнику — он вычтен дважды, прибавляем обратно. В коде:
S = P[r2+1][c2+1] - P[r1][c2+1] - P[r2+1][c1] + P[r1][c1]
Это и есть формула «плюс — минус — минус — плюс», от которой так часто страдают на отладке — но если держать в голове картину «вычесть полосы, добавить пересечение», знаки запоминаются однозначно.
Разбор на примере
, сетка как выше.
Префиксная матрица размера (нулевая строка / столбец — нули):
| 0 | 0 | 0 | 0 | |
| 0 | 1 | 3 | 6 | |
| 0 | 5 | 12 | 21 | |
| 0 | 12 | 27 | 45 |
Проверка . ✓
Запрос : . ✓
Запрос (подпрямоугольник 5 6 / 8 9, сумма ): . ✓
Код решения
Что поменялось
- Размерность состояния выросла на единицу. Теперь префиксный массив двумерный, рекуррент содержит три слагаемых.
- Запрос — четыре обращения вместо двух. Включения-исключения дают плюс-минус-минус-плюс. Самый частый источник WA — перепутанные знаки.
- Память. длинных целых при — около 4 МБ в C++ (
long long), в Python — ощутимо больше (~30–60 МБ). Если упирается в лимит памяти, можно переиспользовать строку «над текущей», сэкономив до памяти, но ценой потери возможности отвечать на запросы после прохода — для постзапросов нужна вся матрица. - Конвенция индексации одна. имеет «лишнюю» нулевую строку и нулевой столбец — это убирает все проверки «а если » из формулы запроса. Альтернатива — хранить размера и явно проверять края — даёт ту же сложность, но больше места для ошибок на границах.
Задача-иллюстрация 3: подсчёт подотрезков с суммой = K (~CF 1500)
Условие
Дан массив длины и целое число . Найти количество подотрезков (непустых, подряд идущих подпоследовательностей) , сумма которых равна ровно .
Ограничения. , .
Пример. , . Ответ — 2 (подотрезки и ).
Пример 2. , . Ответ — 4: , , , . (Считаем руками, чтобы потом сверить с программой.)
Идея
Перейдём к префиксным суммам с обычной конвенцией для суммы . Зафиксируем правый конец подотрезка — какой-то . Условие «сумма равна » означает, что нам нужен такой , чтобы — то есть конкретное число, которое мы можем заранее посчитать.
И вопрос превращается в «сколько раз это конкретное значение встретилось среди уже виденных префиксов ?» — типичный сценарий для хеш-таблицы, где мы храним «значение префикса → сколько раз встречалось».
Алгоритм
- Завести счётчик
cnt(хеш-таблица «значение → количество вхождений»). - Положить в счётчик
cnt[0] = 1— это соответствует , пустому префиксу. - Идти по от до , поддерживая текущее значение инкрементально. На каждом шаге:
- Прибавить к ответу
cnt.get(P[r+1] - K, 0)— это количество подотрезков, заканчивающихся в позиции и имеющих сумму . - Увеличить
cnt[P[r+1]]на 1.
- Прибавить к ответу
Сложность: времени (если хеш-таблица работает за в среднем) и памяти.
Разбор на примере
Возьмём второй пример: , .
Сначала , ответ = 0, .
| искомый ключ | cnt[ключ] | ответ | cnt после шага | |||
|---|---|---|---|---|---|---|
| 0 | 3 | 3 | 0 | 0 | ||
| 1 | 4 | 7 | 1 | 1 | ||
| 2 | 7 | 14 | 1 | 2 | ||
| 3 | 2 | 16 | 0 | 2 | ||
| 4 | -3 | 13 | 0 | 2 | ||
| 5 | 1 | 14 | 1 | 3 | ||
| 6 | 4 | 18 | 0 | 3 | ||
| 7 | 2 | 20 | 1 | 4 |
Ответ — 4, совпадает с подсчётом руками. ✓
Код решения
Что поменялось
- От запроса к подсчёту. Префиксы здесь не отвечают на «сумма », а помогают за один проход найти число пар с заданной суммой.
- Хеш-таблица как универсальный счётчик. Значения префиксных сумм могут быть любыми (включая отрицательные и большие); массив-счётчик не подойдёт, нужна именно хеш-таблица.
- Порядок «сначала прибавить, потом записать». Это критично: если записать в
cntдо прибавления к ответу, мы засчитаем сам — но подотрезок с суммой должен соответствовать , то есть строго раньше. Поменяв порядок, при и нулевых элементах получим переучёт. - Инициализация
cnt[0] = 1. Это считает «пустой префикс» — соответствует подотрезку, начинающемуся с самого первого элемента и имеющему сумму . Без этой строки потеряется часть ответа.
Типичные ошибки
- Off-by-one в индексации. Самая массовая категория ошибок. Лечится одной мерой: с самого начала зафиксировать конвенцию ( — сумма первых элементов, индексы от до ) и не отступать. Перевод запроса — включительно — в , и больше не задумываться.
- Перепутанные знаки в 2D inclusion-exclusion. Формула «плюс — минус — минус — плюс» легко пишется на автомате с ошибкой («минус — плюс — плюс — минус»). Проверка — на маленьком примере 2×2 или 3×3 вручную, как в разборе выше: одна несовпавшая клетка ловит ошибку до отправки.
- Переполнение в C++. слагаемых до дают сумму до — это ещё в
long long, но если возводить в квадрат или умножать — нужен__int128или модульная арифметика. В Python целочисленных переполнений нет, поэтому язык прощает эту ошибку. - Хеш-атака на
unordered_mapв C++. Дефолтный хешstd::hash<long long>уязвим к специально подобранным входам и может деградировать до . На Codeforces регулярно встречаются «анти-хеш» тесты. Решение — кастомный хеш (Splitmix64-подобный) или__gnu_pbds::gp_hash_table. В Pythondictиспользует рандомизированный seed, но на массовом ключе типа целых чисел — этот класс атак реже выживает на 2 секундах TL. - Префиксы для не-аддитивной операции. Попытка построить «префиксный минимум» и ответить на «минимум на » через — одна из самых частых ошибок начинающих. У
minнет обратной операции, и формула не работает: для произвольного отрезка нужен sparse table или сегментное дерево. - Забыть
cnt[0] = 1в задаче «сумма = K». Без этой строки теряется случай, когда подходящий подотрезок начинается с — половина или треть ответа уходит в минус. - Переполнение ответа. Количество подотрезков — до при . В C++ счётчик ответа должен быть
long long. В Python — без забот.
Когда префиксы НЕ подходят
Несколько случаев, когда шаблон с виду применим, а на деле нет.
- Массив изменяется между запросами. После каждого update пришлось бы пересчитывать за , и общая сложность при больших непригодна. Здесь нужны Fenwick (BIT) или сегментное дерево: они дают и на запрос, и на обновление.
- Запрос — не аддитивная операция. Min, max, gcd на отрезке, число различных элементов — стандартный шаблон префиксов не работает (нет обратимости). Sparse table — для idempotent-операций (min/max/gcd) на статичном массиве, отвечает за . Сегментное дерево — для всех операций, но за .
- Запрос требует подмножество, не подсегмент. Если выбираются произвольные элементы (не подряд идущие) — прямой шаблон префиксов не применим. Здесь часто помогают сортировка, биткарта или совсем другая структура.
- Ограничения на суммы по «окну фиксированной ширины». Технически префиксы работают, но эффективнее скользящее окно с двумя указателями — без аллокации памяти под массив .
Полезное правило: если в условии есть слова «обновить » — забыть про префиксы и сразу начинать с Fenwick.
Связанные техники
- Difference array (массив разностей) — обратная сторона префиксов. Помогает применить обновлений вида «прибавить на отрезке » за суммарно: каждое обновление — два точечных изменения в массиве разностей, в конце один проход с накоплением даёт итоговый массив.
- Скользящее окно (sliding window) и два указателя. Когда границы окна монотонно «едут» по массиву, два указателя дают без явного префиксного массива. Префиксы — более универсальный инструмент, скользящее окно — более экономный по памяти и часто более идиоматичный для конкретных задач.
- DP с префиксными суммами. Часто переход в DP вида удаётся ускорить с до или , если подставить «сумму » и заметить, что задача сводится к «минимуму на префиксе» — тогда поддерживается одной переменной.
- Fenwick (BIT) и сегментное дерево. Когда массив изменяется. Ответ за , обновление за , общая сложность .
Каждая из этих техник идёт в следующих частях серии «Алгоритмические основы» — сначала как самостоятельный шаблон, потом в комбинации с префиксами и DP.
Итого
- Техника: , . Сумма .
- Сложность: препроцессинг + на запрос. На запросах — .
- 2D: , ответ на запрос — формула включений-исключений из четырёх членов.
- Сигналы в условии: «сумма на отрезке/в подматрице», «много запросов», «массив фиксирован», «подсчитать подотрезки с суммой = K».
- Типичные ловушки: off-by-one на границах, знаки в 2D, переполнение в C++, забытая инициализация
cnt[0] = 1в counting-варианте, попытка распространить шаблон наmin/max. - Когда не подходит: массив изменяется (Fenwick/сегментное дерево), не-аддитивная операция (sparse table), произвольное подмножество вместо подсегмента.