Один предварительный проход превращает сумму на любом отрезке в одно вычитание. Тот же приём вместе с хеш-таблицей находит подотрезок с заданной суммой за линейное время.
- награда, полученная агентом на шаге
- номер или количество: индекс шага, число соседей, кластеров или позиций
- суммирование по всем перечисленным элементам
Ключевые тезисы
- Сумма на отрезке за O(1) после O(n) предподсчёта — базовый обмен памяти на время.
- Префикс плюс ищут не сам отрезок, а его левую границу — и работают там, где ломается об отрицательные числа.
- Обратная операция — разностный массив: много прибавлений на отрезках за константу каждое, значения восстанавливаются одним проходом.
Какую задачу решает
Сумма на отрезке считается проходом по нему — и это приемлемо ровно один раз. Когда запросов много, повторное сложение одних и тех же чисел становится главным расходом: наивное решение делает операций там, где хватает .
Приём тривиален и от этого особенно ценен: один проход запоминает сумму всего, что стоит до каждой позиции, и дальше сумма любого отрезка — разность двух чисел. В связке с хеш-таблицей та же идея отвечает на вопрос «сколько подотрезков имеют сумму k» — причём и при отрицательных значениях, где неприменимо.
Подробный разбор
Сумма на отрезке считается проходом по нему — это нормально ровно один раз. Когда запросов сто тысяч, а массив на миллион элементов, повторное сложение одних и тех же чисел становится главным расходом: операций там, где хватило бы миллиона.
Вторая форма той же боли — «сколько подотрезков имеют сумму k». Перебор пар границ даёт отрезков, и внутри каждого снова складываются те же элементы.
Запомнить сумму всего, что стоит до каждой позиции. Тогда сумма любого отрезка — разность двух запомненных чисел: общая часть сокращается.
Это обмен памяти на время в чистом виде: один проход и массив той же длины — и после этого каждый запрос стоит одну операцию вместо прохода.
индекс: 0 1 2 3 4
a: 3 5 2 7 4
P: 0 3 8 10 17 21 P[i] = a[0] + ... + a[i-1]
0 1 2 3 4 5 индекс в P
сумма a[2..4] = P[5] - P[2] = 21 - 8 = 13
(2 + 7 + 4 = 13)Тот же массив префиксов отвечает и на вопрос о числе подотрезков с суммой . Условие переписывается как : правая граница у нас в руках, левую достаточно узнать среди уже встреченных.
| Элемент | Накопленная сумма | Ищем сумму (текущая − 5) | Встречалась раньше | Найдено отрезков |
|---|---|---|---|---|
| 1 | 1 | −4 | нет | 0 |
| 2 | 3 | −2 | нет | 0 |
| 3 | 6 | 1 | да, после первого элемента | 1 — это отрезок 2 + 3 |
P = [0] * (len(a) + 1)
for i, x in enumerate(a):
P[i + 1] = P[i] + x
sum_lr = P[r + 1] - P[l] # сумма a[l..r] включительно
# сколько подотрезков имеют сумму k
seen = {0: 1} # пустой префикс
total = answer = 0
for x in a:
total += x
answer += seen.get(total - k, 0)
seen[total] = seen.get(total, 0) + 1- награда, полученная агентом на шаге
- номер или количество: индекс шага, число соседей, кластеров или позиций
- суммирование по всем перечисленным элементам
- Время — на построение и на запрос; вариант с хеш-таблицей — на всё.
- Память — на массив префиксов, на таблицу встреченных сумм.
- Предусловие — массив не меняется между запросами.
Сумма телескопируется: , и всё, что стоит левее , сокращается. Никаких предположений о знаках элементов при этом не делается — именно поэтому приём работает там, где скользящее окно неверно.
Нулевой элемент — не украшение, а способ убрать частный случай: отрезок, начинающийся с нулевой позиции, считается той же формулой, что и все остальные. В варианте с хеш-таблицей ему соответствует запись seen[0] = 1, и её пропуск теряет ровно те отрезки, что начинаются с начала массива.
| Сигнал в условии | Вариант приёма |
|---|---|
| Много запросов суммы, массив не меняется | обычные префиксные суммы |
| «Сколько подотрезков с суммой k», есть отрицательные | префикс плюс |
| «Сумма делится на k» | префикс по остатку от деления |
| «Поровну нулей и единиц» | замена нуля на −1 и поиск нулевой суммы |
| Много прибавлений на отрезках, ответ в конце | разностный массив |
| Сумма по прямоугольнику | двумерные префиксы |
в чужом коде: sum(a[i:j]) внутри цикла. Это скрытый квадрат, и почти всегда он снимается префиксами правкой в три строки.
| Вариант | Что накапливаем | Задача |
|---|---|---|
| Сумма | сумма и среднее на отрезке | |
| Двумерный | сумма прямоугольника от угла | интегральное изображение, суммы по областям |
| По остатку | подотрезки с суммой, делящейся на k | |
| По XOR | подотрезки с заданным XOR | |
| Разностный массив | накапливаем изменения, а не значения | много прибавлений на отрезках |
| Два префикса сразу | суммы и суммы квадратов | на отрезке за константу |
исходно: 0 0 0 0 0
отметки: d[1] += 3, d[4] -= 3
d: 0 3 0 0 -3
префиксы d: 0 3 3 3 0 <- итоговый массив- Массив изменился. Одна правка обесценивает весь хвост префиксов; для изменяемых данных нужно дерево Фенвика.
- Забытая запись `seen[0] = 1`. Теряются отрезки, начинающиеся с начала массива, — на большинстве тестов этого не видно.
- Границы. Решите один раз, что означает
P[i]— сумму до позиции или включая её, — и держитесь этого во всём коде. - Переполнение. Сумма миллиона больших чисел выходит за 32-битный тип задолго до конца массива.
- Вещественные числа. Разность двух больших сумм теряет точность; для
floatлучше суммировать отрезок честно или применять компенсированное суммирование.
| Приём | Когда он лучше | В чём разница |
|---|---|---|
| Скользящее окно | значения неотрицательны, нужно самое длинное или короткое | памяти, но требует монотонности |
| Дерево Фенвика | массив меняется между запросами | на запрос и на обновление |
| Хеш-таблица | условие не про сумму, а про наличие элемента | другой ключ, тот же принцип узнавания |
| Бинарный поиск | префиксы монотонны (все элементы неотрицательны) | поиск границы отрезка по сумме |
- Почему префиксные суммы работают с отрицательными числами, а — нет?
- Чему равно
P[0]и что сломается, если его не заводить? - Сумма делится на k: что класть в хеш-таблицу и почему остаток, а не сама сумма?
- Массив меняется после каждого десятого запроса — что выгоднее: перестраивать префиксы или взять дерево Фенвика?
- Как посчитать на произвольном отрезке за константу?
Чем заменяют
Видеолекции
Тайм-кода именно на эту тему в записях нет. Но глава «Алгоритмы и структуры данных» разобрана в курсе целиком — с той оговоркой, что место в записи придётся искать самому.
Связанные темы
Два указателя и окно
Two Pointers and Sliding Window90%
Два указателя: семейство приёмов · Алгоритмы и структуры данныхЗонтичная тема: что общего у четырёх приёмов с двумя индексами и как выбрать нужный. Сами приёмы разобраны каждый на своей странице.
Opposite-Direction Pointers90%
Встречные указатели · Алгоритмы и структуры данныхДва индекса сходятся с концов отсортированного массива. На каждом шаге отбрасывается конец, который заведомо не даст лучшего ответа, — и вместе с ним целая полоса пар.
Sliding Window90%
Скользящее окно · Алгоритмы и структуры данныхОкно между двумя границами: правая расширяет, левая чинит. Состояние не пересчитывается, а обновляется инкрементально — отсюда линейное время на задачах о подотрезках.
Fast and Slow Pointers90%
Быстрый и медленный указатель · Алгоритмы и структуры данныхДва указателя идут в одну сторону с разной скоростью. Если структура зациклена, быстрый догоняет медленного — цикл находится без журнала посещённых узлов.
Same-Direction Pointers90%
Два указателя в одну сторону · Алгоритмы и структуры данныхСлияние, пересечение и запись на месте: каждому источнику свой указатель, ни один не откатывается назад. Основа сортировки слиянием и merge join в базах данных.
Monotonic Stack and Deque90%
Монотонный стек и дек · Алгоритмы и структуры данныхПриём поверх стека: хранить только тех, для кого ответ ещё не найден. Даёт ближайший больший элемент для всех позиций сразу и максимум в скользящем окне — за один проход.
Всё, что названо в описании, разобрано здесь же или в соседней теме — переходы под каждым определением.
Телескопирование
Общая часть двух префиксов сокращается — отсюда разность вместо сложения.
Разбор ниже: Почему это работаетПрефикс и хеш-таблица
Ищем не отрезок, а его левую границу среди уже встреченных сумм.
Разбор ниже: Механика на игрушечном примереРазностный массив
Обратная операция: прибавление на отрезке за константу.
Разбор ниже: Вариации и параметрыДвумерные префиксы
Сумма прямоугольника за четыре обращения по включению-исключению.
Разбор ниже: ФормализацияТема встречается в тесте по направлениям — ошибки приведут обратно на эту страницу.
Глава «Алгоритмы и структуры данных» последний раз правилась . Нашли ошибку — напишите.