Medio · sumas prefijas
Sumas por rangos
Sumar cada rango a mano cuesta O(longitud del rango) por pregunta, y las preguntas no dejan de llegar. Las sumas prefijas llevan todo el trabajo a una pasada de preparación: prefix[i] es la suma de los i primeros números, así que cualquier rango es una resta.
En una fraseConstruye una vez <code>prefix[i] = prefix[i-1] + a[i]</code> para i = 1..n. La respuesta de [l, r] es <code>prefix[r] - prefix[l-1]</code>: una resta por consulta, con <code>long long</code> para totales de 10^13.
El problema
Entrada: La primera línea trae n y q (1 ≤ n ≤ 10000, 1 ≤ q ≤ 10000). La segunda, n enteros (−10^9 ≤ a_i ≤ 10^9). Luego q líneas con l y r (1 ≤ l ≤ r ≤ n).
Salida: q líneas: para cada consulta, la suma de a[l..r], una por línea.
Ejemplos:
| Entrada | Salida |
|---|---|
5 3 | 15 |
La convención de empezar en 1 trabaja en silencio aquí: prefix[0] = 0 hace que la fórmula funcione con l = 1 sin casos especiales. El error de uno vive justo en esa costura — mira el ejemplo fallido de abajo.
Tu solución
Entrada personalizada para «Ejecutar mi entrada»
La corrección corre en tu navegador contra pruebas ocultas. Un bucle infinito se corta a los 3 segundos — aun así, asegúrate de que todo bucle tenga salida. Tu código se guarda solo mientras escribes.
¿Atascado? Pistas
Pista 1 — Haz que prefix[i] incluya a[i]
Fija el significado prefix[i] = a[1] + … + a[i] y pon prefix[0] = 0. Entonces la suma de a[l..r] telescopa: todo hasta r, menos todo lo anterior a l. La mitad de los errores de uno en este problema vienen de mezclar las dos convenciones.
Pista 2 — El array va de 1, C++ de 0
Declara los arrays una casilla más grandes e ignora la casilla 0 (o guarda ahí el 0), para que a[1..n] y prefix[1..n] queden en sus índices naturales. Pelear con la convención a base de - 1 regados por el código es como nacen los errores de signo.
Pista 3 — int no cabe ahí
Cada elemento cabe en un int (hasta 10^9), pero n de ellos suman 10^13: muy por encima del techo de int, unos 2.1 × 10^9. Declara prefix como array de long long. El desbordamiento no revienta: devuelve un número equivocado en silencio, que es el peor tipo de bug.
Solución (¡inténtalo primero!)
#include <iostream>
using namespace std;
long long prefix[10005];
int main() {
int n, q;
cin >> n >> q;
prefix[0] = 0;
for (int i = 1; i <= n; i++) {
long long x;
cin >> x;
prefix[i] = prefix[i - 1] + x;
}
for (int k = 0; k < q; k++) {
int l, r;
cin >> l >> r;
cout << prefix[r] - prefix[l - 1] << endl;
}
return 0;
}
Las sumas prefijas son la primera estructura que construyes antes de conocer las preguntas — y la misma idea vuelve en Suma máxima de subarray, donde una diferencia de prefijos encuentra la mejor ventana en una pasada. Cuando a las consultas se suman modificaciones, la siguiente herramienta es el array de diferencias; después, los árboles.
Análisis profundo
Por qué funciona
Guarda el total acumulado. prefix[r] - prefix[l-1] cancela todo lo que los dos prefijos comparten y deja exactamente a[l] + … + a[r]. Es el mismo telescopado que hace rápidos los cubos de la criba y del counting sort: construye una estructura y responde con diferencias.
El coste
O(n) en construir, O(1) por consulta, O(n) de espacio. La fuerza bruta sería O(n·q): con n = q = 10000 son 100 millones de sumas frente a 20 mil.
Dónde falla
- Responder
prefix[r] - prefix[l]— se come a[l] en silencio. La fórmula esprefix[l-1], y los tests que solo usan l = 1 esconden el bug. - Guardar prefix en
int: cada elemento cabe, el total no. El desbordamiento da la vuelta en silencio y las respuestas solo parecen plausibles.
Siguiente paso
El truco del total acumulado vuelve como la intuición de Kadane en Suma máxima de subarray — la mejor ventana es una diferencia de dos prefijos.
Dos cosas para recordar
- prefix[i] = a[1] + … + a[i]; un rango es prefix[r] − prefix[l−1].
- Sumar muchos int exige long long — el desbordamiento avisa poco.
🐞 Esta versión falla: ábrela y pruébala
Responde prefix[r] - prefix[l]: se come a[l] sin decir nada
#include <iostream>
using namespace std;
long long prefix[10005];
int main() {
int n, q;
cin >> n >> q;
prefix[0] = 0;
for (int i = 1; i <= n; i++) {
long long x;
cin >> x;
prefix[i] = prefix[i - 1] + x;
}
for (int k = 0; k < q; k++) {
int l, r;
cin >> l >> r;
cout << prefix[r] - prefix[l] << endl;
}
return 0;
}