Introducción a las sumas de prefijos
Recursos
| Fuente | Recurso | Notas |
|---|---|---|
| IUSACO | 11 - Prefix Sums | Fuente de este módulo |
| CPH | 9.1 - Sum Queries | Descripción breve |
| PAPS | 11.2.1 - Prefix Precomputation | Motivación e implementación |
Video de YouTube (f0bfiuDjq9A)
Introducción
Las sumas de prefijos (prefix sums) son una técnica para calcular rápidamente la suma de cualquier subarreglo. Al precomputar las sumas acumuladas en tiempo , las consultas posteriores se pueden resolver en tiempo . Esta técnica se puede generalizar a operaciones invertibles, como el XOR bit a bit .
Ejemplo - Static Range Sum
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| YS | Static Range Sum | Muy fácil | en el módulo |
Explicación
Supongamos que tenemos un arreglo de enteros de tamaño indexado desde 1 y queremos calcular el valor de
para pares distintos que cumplen . Usaremos el siguiente ejemplo con :
| Índice | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| 1 | 6 | 4 | 2 | 5 | 3 |
De forma ingenua, para cada consulta podemos recorrer todas las entradas desde el índice hasta el índice y sumarlas. Como hay consultas y cada una requiere como máximo operaciones para calcular la suma, la complejidad temporal total es . En la mayoría de los problemas de este tipo, las restricciones serán , así que es del orden de . Esto no es aceptable; casi con certeza superará el límite de tiempo.
En su lugar, podemos usar sumas de prefijos para procesar estas consultas de suma sobre el arreglo. Designamos un arreglo de sumas de prefijos . Primero, como indexamos el arreglo desde 1, asignamos ; luego, para los índices tales que , definimos el arreglo de sumas de prefijos así:
En esencia, el elemento en el índice del arreglo de sumas de prefijos almacena la suma de todos los elementos del arreglo original desde el índice hasta . Esto se puede calcular fácilmente en con la siguiente fórmula para cada :
Para el caso de ejemplo, el arreglo de sumas de prefijos queda así:
| Índice | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| 0 | 1 | 7 | 11 | 13 | 18 | 21 |
Ahora, cuando queremos consultar la suma de los elementos de entre los índices y inclusive (indexados desde 1), podemos usar la siguiente fórmula:
Usando la definición de los elementos del arreglo de sumas de prefijos, tenemos
Como solo consultamos dos elementos del arreglo de sumas de prefijos, podemos calcular sumas de subarreglos en por consulta, lo cual es mucho mejor que el por consulta que teníamos antes. Ahora, después de un preprocesamiento de para calcular el arreglo de sumas de prefijos, cada una de las consultas toma tiempo . Así, la complejidad temporal total es , que ya debería pasar el límite de tiempo.
Hagamos una consulta de ejemplo y calculemos la suma del subarreglo entre los índices y , inclusive, en el indexado desde 1. Mirando el arreglo original, vemos que esto es
| Índice | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| 1 | 6 | 4 | 2 | 5 | 3 |
Usando sumas de prefijos:
Clic en un índice para L. La suma es prefix[R] − prefix[L−1].
L=2, R=5 → prefix[5] − prefix[1] = 18 − 1 = 17
| Índice | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| 0 | 1 | 7 | 11 | 13 | 18 | 21 |
También se conocen como sumas parciales (partial sums).
Implementación
Complejidad temporal:
En C++ podemos usar
std::partial_sum,
aunque no acorta mucho el código.
#include <bits/stdc++.h>
using namespace std;
vector<long long> psum(const vector<int> &arr) {
vector<long long> psums(arr.size() + 1);
for (int i = 0; i < arr.size(); i++) { psums[i + 1] = psums[i] + arr[i]; }
// or partial_sum(begin(a), end(a), begin(psums) + 1);
return psums;
}
int main() {
int N, Q;
cin >> N >> Q;
vector<int> nums(N);
for (int i = 0; i < N; i++) { cin >> nums[i]; }
vector<long long> prefix_arr = psum(nums);
for (int i = 0; i < Q; ++i) {
int l, r;
cin >> l >> r;
cout << prefix_arr[r] - prefix_arr[l] << "\n";
}
}import java.io.*;
import java.util.*;
public class Main {
static int N, Q;
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
PrintWriter writer = new PrintWriter(System.out);
StringTokenizer st = new StringTokenizer(reader.readLine());
N = Integer.parseInt(st.nextToken());
Q = Integer.parseInt(st.nextToken());
st = new StringTokenizer(reader.readLine());
int[] nums = new int[N];
for (int i = 0; i < N; i++) { nums[i] = Integer.parseInt(st.nextToken()); }
long[] prefix_arr = psum(nums);
for (int i = 0; i < Q; i++) {
int l, r;
st = new StringTokenizer(reader.readLine());
l = Integer.parseInt(st.nextToken());
r = Integer.parseInt(st.nextToken());
writer.println(prefix_arr[r] - prefix_arr[l]);
}
reader.close();
writer.close();
}
public static long[] psum(int[] arr) {
long[] psums = new long[N + 1];
for (int i = 0; i < N; i++) { psums[i + 1] = psums[i] + arr[i]; }
return psums;
}
}def psum(arr):
psums = [0]
for i in arr:
psums.append(psum[-1] + i)
return psums
N, Q = map(int, input().split())
nums = list(map(int, input().split()))
prefix_arr = psum(nums)
for i in range(Q):
l, r = map(int, input().split())
print(prefix_arr[r] - prefix_arr[l])Un enfoque alternativo es usar itertools.accumulate.
Nótese que hay que agregar un al inicio del arreglo.
Si se usa una versión más reciente, también se puede usar el parámetro opcional initial=0.
import itertools
def psum(arr):
return [0] + list(itertools.accumulate(arr))
# BeginCodeSnip{Same code as above}
N, Q = map(int, input().split())
nums = list(map(int, input().split()))
prefix_arr = psum(nums)
for i in range(Q):
l, r = map(int, input().split())
print(prefix_arr[r] - prefix_arr[l])
# EndCodeSnipProblemas
| Hecho | Fuente | Nombre | Dificultad | Tags | Solución |
|---|---|---|---|---|---|
| Silver | Breed Counting | Muy fácil | Prefix Sums | Solución | |
| Silver | Hoof Paper Scissors | Muy fácil | Prefix Sums | Solución | |
| AC | GCD on Blackboard | Fácil | Prefix Sums | Solución | |
| CF | ★ Good Subarrays | Fácil | Prefix Sums, Math | Solución | |
| CSES | Subarray Divisibility | Fácil | Prefix Sums | Solución | |
| CSES | Subarray Sums II | Fácil | Prefix Sums | Solución | |
| Silver | Subsequences Summing to Sevens | Fácil | Prefix Sums | Solución | |
| CF | Alternating String | Normal | Prefix Sums | Solución | |
| AC | Multiple of 2019 | Normal | Prefix Sums | Solución | |
| CF | ★ Running Miles | Normal | Prefix Sums | Solución | |
| Silver | ★ Farmer John's Favorite Operation | Normal | Prefix Sums | Solución | |
| CF | Irreducible Anagrams | Difícil | Prefix Sums | Solución | |
| CF | Sum of Segments | Difícil | Prefix Sums | — |
Quiz
Pregunta 1/4