Range Sum Query (RSQ)

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;
    }
}

RSQ com atualização dos valores.

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.

Veja como implementar uma fenwick tree aqui.

Veja como implementar uma segment tree aqui.