Implementação de Árvore Segmentada para Soma de Intervalos

Este artigo aborda a solução de um problema clássico de estrutura de dados: realizar operações de atualização de ponto único e consulta de soma de intervalo em uma sequência. O problema exige processar até 500.000 elementos e 500.000 operações, tornando abordagens ingênuas inviáveis. A estrutura de dados escolhida é a árvore segmentada (segment tree), que oferece complexidade O(log n) para ambas as operações.

Descrição do Problema

Dado um array inicial de N inteiros, o programa deve lidar com M operações:

  • Atualização (tipo 1): Somar um valor k ao elemanto na posição x.
  • Consutla (tipo 2): Retornar a soma dos elementos no intervalo [x, y].

Estrutura da Árvore Segmentada

Cada nó da árvore armazena um intervalo [l, r] e a soma dos elementos nesse intervalo. A árvore é construída recursivamente: nós folha representam um único elemento, e nós internos combinam as somas dos filhos esquerdo e direito.

Variáveis e Constantes

const int MAXN = 5e5 + 10;
int arr[MAXN];

struct Node {
    int left, right;
    int sum;
} seg[4 * MAXN];

Construção da Árvore (build)

void build(int idx, int l, int r) {
    seg[idx].left = l;
    seg[idx].right = r;
    if (l == r) {
        seg[idx].sum = arr[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(idx * 2, l, mid);
    build(idx * 2 + 1, mid + 1, r);
    seg[idx].sum = seg[idx*2].sum + seg[idx*2+1].sum;
}

Atualização de Ponto Único (update)

Para somar k ao elemento na posição pos, percorremos a árvore recursivamente. Se o intervalo do nó contém pos, adicionamos k à soma do nó. Continuamos descendo até atingir a folha correspondente.

void update(int idx, int pos, int val) {
    if (seg[idx].left > pos || seg[idx].right < pos) return;
    seg[idx].sum += val;
    if (seg[idx].left == seg[idx].right) return; // folha
    update(idx * 2, pos, val);
    update(idx * 2 + 1, pos, val);
}

Consulta de Soma de Intervalo (query)

Para obter a soma no intervalo [ql, qr], verificamos três casos:

  • Se o intervalo do nó está totalmente fora de [ql, qr], retornamos 0.
  • Se o intervalo do nó está totalmente dentro de [ql, qr], retornamos a soma armazenada.
  • Caso contrário, combinamos as consultas recursivas dos filhos.
int query(int idx, int ql, int qr) {
    if (seg[idx].right < ql || seg[idx].left > qr) return 0;
    if (seg[idx].left >= ql && seg[idx].right <= qr) return seg[idx].sum;
    int left_sum = query(idx * 2, ql, qr);
    int right_sum = query(idx * 2 + 1, ql, qr);
    return left_sum + right_sum;
}

Função Principle

int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> arr[i];
    build(1, 1, n);
    while (m--) {
        int op, a, b;
        cin >> op >> a >> b;
        if (op == 1) {
            update(1, a, b);
        } else {
            int result = query(1, a, b);
            cout << result << endl;
        }
    }
    return 0;
}

Exemplo de Execução

Entrada:

5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4

Saída:

14
16

Análise de Complexidade

  • Tempo: Construção O(n), cada operação O(log n). Para o limite de 500.000 operações, é eficiente.
  • Espaço: O(n) para o array original e O(4n) para a árvore, dentro da memória especificada.

Essa implementação resolve o problema dentro dos limites de tempo e memória, utilizando a estrutura clássica de árvore segmentada para manipulação de intervalos e pontos.

Tags: segment tree árvore segmentada soma de intervalo atualização de ponto estrutura de dados

Publicado em 7-6 01:14