Skip to content
Clases

Prefix Sum & Difference Array

Supongamos que tenemos un arreglo de NN números y nos hacen QQ preguntas. Cada pregunta es: “¿Cuál es la suma de los elementos entre el índice LL y RR?”

  • Fuerza Bruta: Usar un ciclo for para cada pregunta.
    • Complejidad: O(NQ)O(N \cdot Q). (Muy lento si N,Q105N, Q \le 10^5).
  • Nuestro Objetivo: Responder cada pregunta en tiempo constante O(1)O(1).

Un arreglo de Sumas de Prefijos (Cumulative Sum) es una estructura donde cada elemento P[i] guarda la suma de todos los elementos originales desde el índice 0 hasta i.

[Image of prefix sum array visualization]

Para un arreglo original AA, construimos PP: P[i]=P[i1]+A[i]P[i] = P[i-1] + A[i]

Una vez construido, la suma del rango [L,R][L, R] se calcula restando el prefijo que “sobra”: Suma(L,R)=P[R]P[L1]\text{Suma}(L, R) = P[R] - P[L-1]

(Nota: Si L=0L=0, la suma es simplemente P[R]P[R]).

#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<long long> A = {2, 4, 1, 7, 6};
int n = A.size();
// 1. Crear arreglo de prefijos (tamaño n)
vector<long long> P(n);
// 2. Construcción en O(N)
P[0] = A[0];
for (int i = 1; i < n; ++i) {
P[i] = P[i-1] + A[i];
}
// 3. Consulta de rango [2, 4] en O(1)
// Rango: {1, 7, 6} -> Suma: 14
int L = 2, R = 4;
long long suma;
if (L == 0) suma = P[R];
else suma = P[R] - P[L-1];
cout << "Suma del rango [2, 4] es: " << suma << endl;
return 0;
}

Es el espejo del Prefix Sum. Un arreglo SS donde cada elemento S[i] contiene la suma desde ii hasta el final (N1N-1).

  • Se construye de derecha a izquierda.
  • Útil cuando necesitas dividir el arreglo en dos partes y comparar la suma izquierda vs. derecha.

Si Prefix Sum es para consultas rápidas, el Arreglo de Diferencias es para actualizaciones de rango rápidas.

Definición: D[i] = A[i] - A[i-1]

  • La magia: El arreglo original AA es el Prefix Sum del arreglo de diferencias DD.

Queremos sumar un valor xx a todos los elementos desde LL hasta RR. En lugar de un ciclo, hacemos solo 2 operaciones:

  1. Sumar xx en el inicio del rango: D[L] += x
  2. Restar xx justo después del final: D[R+1] -= x

Esto crea un efecto de “onda” que, al reconstruir el arreglo con sumas de prefijos, aumenta todo el rango y se cancela justo donde termina.

// Inicialmente todo en 0
vector<int> diff(n + 1, 0);
// Operación: Sumar 5 al rango [2, 4]
int L = 2, R = 4, val = 5;
diff[L] += val; // Inicio del cambio
diff[R + 1] -= val; // Fin del cambio
// Reconstruir el arreglo final
vector<int> resultado(n);
int actual = 0;
for(int i = 0; i < n; i++) {
actual += diff[i];
resultado[i] = actual;
}

Funciona exactamente igual que la suma. XOR(L,R)=Pxor[R]Pxor[L1]\text{XOR}(L, R) = P_{xor}[R] \oplus P_{xor}[L-1]

  • ¿Por qué funciona? Porque el XOR es su propia inversa (AA=0A \oplus A = 0). Al hacer XOR del rango total con el prefijo anterior, los elementos repetidos se cancelan.

¡Cuidado! No existe el “Prefix GCD” simple.

  • Razón: El GCD no tiene operación inversa. No puedes “restar” un GCD.
  • Solución: Para consultas de rango de GCD, Mínimo o Máximo, necesitas estructuras avanzadas como Sparse Table (estático) o Segment Tree (dinámico).