Desafios de Estruturas de Dados e Algoritmos em Concurso

Nesta publicação, analisamos quatro problemas de programação competitiva que envolvem estruturas de dados avançadas e técnicas algorítmicas. Cada solução aborda desafios específicos, como consultas em intervalos, atualizações dinâmicas e manipulação de árvores.

Problema A: Contagem com Persistência

O primeiro problema explora o uso de árvores de segmento persistnetes para responder consultas eficientes em intervalos. O código implementa uma estrutura que permite contar elementos dentro de um intervalo específico, aproveitando a persistência para manter versões anteriores da árvore.

// Exemplo de estrutura persistente
#include <bits/stdc++.h>
using namespace std;
#define itn int
constexpr int TAM = 3000006;
int n, m, tipo;
int resp, total, topo;
int esq[TAM], dir[TAM], ant[TAM], nums[TAM], pilha[TAM], raiz[TAM];
struct Segmento{ int esq, dir, soma; } seg[TAM*30];
struct Aresta{ int v, prox; } arestas[TAM];
int cabeca[TAM], contArestas;

void inserir(int x, int& y, int l, int r, int pos){
    if (!y || y == x) y = ++total, seg[y].soma = seg[x].soma;
    seg[y].soma++;
    if (l == r) return;
    int mid = (l+r)>>1;
    if (pos <= mid)
        inserir(seg[x].esq, seg[y].esq, l, mid, pos);
    else
        inserir(seg[x].dir, seg[y].dir, mid+1, r, pos);
}

int consultar(int x, int y, int l, int r, int a, int b){
    if (a <= l && r <= b) return seg[y].soma - seg[x].soma;
    int mid = (l+r)>>1;
    if (b <= mid) return consultar(seg[x].esq, seg[y].esq, l, mid, a, b);
    if (a > mid) return consultar(seg[x].dir, seg[y].dir, mid+1, r, a, b);
    return consultar(seg[x].esq, seg[y].esq, l, mid, a, b) + consultar(seg[x].dir, seg[y].dir, mid+1, r, a, b);
}

int main(){
    ios::sync_with_stdio(false);
    cin >> n >> m >> tipo;
    // Leitura e processamento...
    return 0;
}

Problema B: Soma Absoluta em Árvores

Este problema utiliza a decomposição de árvores (heavy-light decomposition) combinada com duas árvores de segmento para gerenciar valores positivos e negativos separadamente. A ideia central é que ao adicionar um valor positivo, os números negativos só mudam de sinal uma vez, permitindo atualizações pontuais eficientes.

// Estruturas para soma e máximo
struct ArvoreSoma{
    int tree[TAM*4], lazy[TAM*4], tamanho[TAM*4];
    void construir(int x, int l, int r){
        if(l == r){
            tree[x] = novo[l] >= 0 ? novo[l] : 0;
            tamanho[x] = novo[l] >= 0 ? 1 : 0;
            return;
        }
        int mid = (l+r)>>1;
        construir(x<<1, l, mid);
        construir(x<<1|1, mid+1, r);
        atualizar(x);
    }
    void atualizar(int x){
        tree[x] = tree[x<<1] + tree[x<<1|1];
        tamanho[x] = tamanho[x<<1] + tamanho[x<<1|1];
    }
    // Métodos para adicionar lazy e consultar...
};

struct ArvoreMax{
    struct Par{ int valor, indice; };
    Par maximo[TAM*4];
    int tree[TAM*4], lazy[TAM*4], tamanho[TAM*4];
    // Implementação similar com busca de máximos negativos...
};

Problema C: Computação em Blocos

A solução utiliza divisão em blocos com estrutura de árvore BIT (Fenwick) para consultas em intervalo. Um aspecto crucial é a tabela f[i][j] que armazena o número de nós do caminho da raiz até i que pertencem ao bloco j, permitindo atualizações rápidas.

int f[TAM][320], bloco[TAM], valor[TAM];
void dfs(int x, int pai){
    for(int i=1; i<=numBlocos; i++) f[x][i] = f[pai][i];
    f[x][bloco[x]]++;
    esq[x] = ++tempo;
    peso[x] = valor[x];
    for(int v : adj[x]){
        if(v == pai) continue;
        dfs(v, x);
        peso[x] += peso[v];
    }
    dir[x] = tempo;
}

Problema D: Caminhos Mínimos e Dijkstra

Este problema apresenta uma variação do algoritmo de Dijkstra onde, além de encontrar distâncias mínimas, ordenam-se os vértices por distância. A abordagem utiliza múltiplas execuções do algoritmo combinadas com estruturas de dados para enumerar arestas e vértices intermediários.

Revisão de Estruturas

Durante a preparação para a prova inicial, foram revisados conceitos fundamentais:

  • BIT (Fenwick Tree) para consultas de prefixo
  • Árvores de Segmento com lazy propagation
  • Árvores Persistentes (Chairman Tree)
  • Union-Find com variações (compressão de caminho, união por peso, reversão)

Estes problemas demonstram a importância de dominar estruturas de dados avançadas e técnicas de otimização para competições de programação.

Tags: árvore-de-segmento persistência heavy-light-decomposition fenwick-tree dijkstra

Publicado em 7-2 06:02