Este problema consiste em calcular a soma dos valores de um dado intervalo de um conjunto de números.
Por exemplo, se o conjunto dado for:
1 5 6 8 8 0 4 -4
rsq(1,3) = 19, pois queremos a soma dos elementos entre os índices 1 e 3: 5 + 6 + 8
rsq(0,2) = 12
rsq(3,6) = 20
e assim por diante.
Seja n o número de elementos do array;
Sejam i e j os dois índices do intervalo ( 0 <= i <= j < n);
Seja sum um array global: int sum[n];
Seja array um array global: int array[n];
Caso você deseje fazer apenas uma consulta, então esse problema é resolvido fazendo simplesmente um loop de i a j e somando os valores:
int rsq(int i, int j){ int sum = 0; for (int k=i; k <=j; ++k) { sum += array[k]; } return sum;}Perceba que essa função é O(n). Logo, se forem feitas m consultas, teremos O(mn). Para m pequeno, essa pode ser uma solução aceitável. Porém, para m grande, podemos construir uma solução de maneira bem fácil em O(1), bastando fazer um pré-processamento em O(n). Além disso, o pré-processamento pode ser feito na leitura dos dados, o que teria que ser feito de qualquer maneira.
A ideia é criar um outro array para acumular a soma dos elementos. Quando quisermos saber a soma de um intervalo, basta subtrair os dois valores da soma acumulada:
/**Rodrigo Paes*/#include <bits/stdc++.h>using namespace std;#define MAX_N 1000#define FILL_0(a) memset(a, 0, sizeof a)int sum[MAX_N];int array[MAX_N];/*Range sum query[i, j]O(1)*/int rsq(int i, int j){ int start = i >0 ? sum[i-1] : 0; return sum[j]-start;}int main(){ int n, q, s, e; cin >> n; FILL_0(array); FILL_0(sum); for (int i=0; i<n; ++i) { cin >> array[i]; // pré-processamento: constrói o array de soma, acumulada. sum[i] = i>0? sum[i-1] + array[i] : array[i]; } cin >> q; while (q--) { cin >> s >> e; cout << rsq(s,e) << endl; }}Suponha que os valores do array possam ser alterados entre uma query e outra. Nesse caso, para cada alteração do array, teríamos que atualizar o array sum da posição da alteração até o final do array. Ou seja, uma operação em O(n).
Duas formas de resolver esse problema são com Segment trees e com Fenwick trees. Em ambas, a consulta e a atualização de valores são feitas em O(log(n)) e a sua construção é feita em O(n).
Originalmente a Fenwick tree é utilizada para o cálculo da soma de um prefixo, Ou seja, qual o valor da soma acumulado até um determinado índice. Entretanto, ela pode ser adaptada para retornar a soma de um intervalo bastando chamar a fenwick duas vezes, uma com o índice do ínicio do intervalo e outra com o fim do intervalo, e depois subtraíndo esses valores.
Portanto, essas opções só valem a pena se forem feitas muitas modificações e/ou muitas consultas.