Resolução de Desafios Algorítmicos: Programação Dinâmica, Grafos e Teoria dos Números

Análise do Problema de Mineração (Mine)

Este problema exige calcular o número de maneiras válidas para preencher uma sequência linear onde cada posição indica informações sobre bombas adjacentes. A abordagem utiliza Programação Dinâmica (DP). Definimos o estado como dp[indices][valor_atual][valor_previo], representando até qual posição da sequência chegamos, qual é o estado atual da célula e qual foi o estado da célula imediatamente anterior.

Os valores das células variam de 0 a 3, indicando quantas bombas vizinhas existem, ou são classificados como bomba. É necessário realizar uma verificação rigorosa das transições para garantir que as restrições de vizinhença sejam respeitadas. O algoritmo deve lidar com entradas genéricas onde certos caracteres representam dígitos fixos ou placeholders incertos.

#include <bits/stdc++.h>
using namespace std;

const int TAM_MAX = 1000005;
const long long MODULO = 1e9 + 7;
char entrada[TAM_MAX];
int configuracao[TAM_MAX];
long long memo[TAM_MAX][5][5]; 
int tamanho_str;

// Normaliza o valor módulo MODULO
inline void ajustar_modulo(long long &val) {
    if(val >= MODULO) val -= MODULO;
}

bool eh_bomba(int idx) {
    return (configuracao[idx] == 3 || configuracao[idx] == 4);
}

// Validação inicial da string para consistência básica
bool verificar_validade() {
    for (int i = 1; i <= tamanho_str; ++i) {
        if (configuracao[i] == 4) continue; // Pulo se for '?'
        
        // Regras de lógica para minas baseadas nas pistas numéricas
        if (configuracao[i] == 1) {
            bool esquerda = (i > 1 && eh_bomba(i-1));
            bool direita = (i < tamanho_str && eh_bomba(i+1));
            if (!(esquerda ^ direita)) return false; // Deve ter exatamente uma vizinha bomba
        } else if (configuracao[i] == 2) {
             if (!eh_bomba(i-1) || !eh_bomba(i+1)) return false;
        }
    }
    return true;
}

int main() {
    scanf("%s", entrada + 1);
    tamanho_str = strlen(entrada + 1);

    // Conversão de caracteres para valores inteiros
    for (int i = 1; i <= tamanho_str; ++i) {
        if (entrada[i] == '0') configuracao[i] = 0;
        else if (entrada[i] == '1') configuracao[i] = 1;
        else if (entrada[i] == '2') configuracao[i] = 2;
        else if (entrada[i] == '*') configuracao[i] = 3;
        else if (entrada[i] == '?') configuracao[i] = 4;
    }

    if (tamanho_str == 1) {
        if (configuracao[1] == 0 || configuracao[1] == 3) cout << 1 << endl;
        else cout << 0 << endl;
        return 0;
    }

    if (!verificar_validade()) {
        cout << 0 << endl;
        return 0;
    }

    // Inicialização do DP para a primeira posição
    memset(memo, 0, sizeof(memo));
    if (configuracao[1] != 4) {
        if (configuracao[1] == 0) memo[1][0][0] = 1;
        else if (configuracao[1] == 1) memo[1][1][0] = 1;
        else if (configuracao[1] == 3) memo[1][3][0] = 1;
    } else {
        memo[1][0][0] = memo[1][1][0] = memo[1][3][0] = 1;
    }

    // Transições de estado DP
    for (int i = 2; i <= tamanho_str; ++i) {
        for (int curr = 0; curr <= 3; ++curr) {
            if (configuracao[i] != 4 && configuracao[i] != curr) continue;

            for (int prev = 0; prev <= 3; ++prev) {
                // Lógica de transição baseada na compatibilidade entre células adjacentes
                // Exemplo simplificado de combinação de estados anteriores e atuais
                if (curr == 0) {
                     if (prev == 0 || prev == 1) // Dependendo da configuração exata
                         ajuste...
                }
                // ... (A lógica completa envolve múltiplas condições de vizinhança)
                // Adicione transições apropriadas aqui mantendo a complexidade O(N)
            }
        }
    }
    
    // Soma final das configurações válidas
    // Este bloco seria expandido para cobrir todas as combinações finais possíveis
    // Baseado no código original, a soma considera as restrições do último elemento
    
    return 0;
}

Lógica de Fluxo de Água (Water)

Para determinar o nível de água em cada bloco de uma grade retangular, precisamos encontrar o caminho de saída com o menor "custo máximo". O custo de um caminho é definido pela altura máxima encontrada nele. O nível final é igual à altura inicial mais a profundidade da água acumulada. Isso se traduz em um problema de caminho mínimo onde a função de ponderação não é aditiva, mas sim baseada no máximo (max(dist, peso_aresta)).

Construímos um grafo onde cada célula é um nó conectado aos seus quatro vizinhos. Nós adicionais conectam todos os blocos da borda a um nó fonte virtual. Executamos o algoritmo de Dijkstra a partir dese nó fonte para calcular o limite mínimo de saída para cada coordenada.

#include <iostream>
#include <queue>
#include <vector>
#include <cstring>
#include >algorithm>
using namespace std;

const int MAX_N = 305;
const int INF = 0x3f3f3f3f;

struct Aresta {
    int destino;
    int peso;
};

int n_linhas, m_colunas;
int alturas[MAX_N][MAX_N];
int nivel_minimo[MAX_N][MAX_N];
bool visitado[MAX_N][MAX_N];
int deltas_x[] = {0, 1, 0, -1};
int deltas_y[] = {1, 0, -1, 0};
int fonte_virtual_id = MAX_N * MAX_N;

// Estrutura para a fila de prioridade do Dijkstra
struct Estado {
    int custo;
    int id_no;
    
    bool operator>(const Estado& outro) const {
        return custo > outro.custo;
    }
};

void executar_dijkstra() {
    memset(nivel_minimo, 0x3f, sizeof(nivel_minimo));
    priority_queue<Estado> pq;
    
    // Conectar fonte virtual às bordas
    for(int i=1; i<=n_linhas; ++i){
        for(int j=1; j<=m_colunas; ++j){
            if(i==1 || i==n_linhas || j==1 || j==m_colunas){
                // Custo é o máximo entre a altura atual e a do vizinho (ou zero)
                int custo = alturas[i][j]; 
                if(nivel_minimo[i][j] > custo){
                    nivel_minimo[i][j] = custo;
                    pq.push({-nivel_minimo[i][j], i*m_colunas+j}); // Negativo para min-heap
                }
            }
        }
    }

    while(!pq.empty()){
        Estado atual = pq.top();
        pq.pop();
        
        int id = atual.id_no;
        int x = (id-1)/m_colunas + 1;
        int y = (id-1)%m_colunas + 1;
        
        if(visitado[x][y]) continue;
        visitado[x][y] = true;

        for(int k=0; k<4; ++k){
            int nx = x + deltas_x[k];
            int ny = y + deltas_y[k];

            if(nx > 0 && nx <= n_linhas && ny > 0 && ny <= m_colunas){
                // Peso da aresta é o máximo das alturas dos dois blocos
                int nova_altura = max(alturas[x][y], alturas[nx][ny]);
                if(nivel_minimo[nx][ny] > nova_altura){ // Lógica modificada para minimax
                    // Revisão: A distância aqui representa o pico máximo no caminho
                    // Corrigindo a lógica de relaxamento padrão para minimax path
                    // O código original usa dist[y] > max(dist[x], len[i])
                    // Aqui adaptando para a estrutura de matriz
                }
            }
        }
    }
}
// A implementação completa requer cuidadosa adaptação da lógica de maximização dentro do relaxamento

Contagem de Divisores Comuns (GCD)

O desafio consiste em manter dinamicamente o número de pares de elementos seleiconados cujo Máximo Divisor Comum (MDC) seja exatamente 1. Para resolver isso eficientemente durante as operações de atualização, aplicamos a Inversão de Möbius. Precisamos monitorar três métricas principais:

  • S: Quantidade de números selecionados que são múltiplos de i.
  • G: Pares selecionados onde ambos são múltiplos de i (calculado como C(S[i], 2)).
  • F: Pares com MDC exatamente igual a i.

A relação fundamental é G[i] = Σ F[d] para todos d múltiplos de i. Usando a função de Möbius, podemos expressar F[1] (que desejamos maximizar) somando as contribuições de todos os múltiplos ponderadas por μ(d/i). Ao adicionar ou remover um número, iteramos sobre seus divisores para atualizar as contagens S e recalculamos a resposta global.

#include <cstdio>
#include <vector>
#include <cmath>
using namespace std;

const int LIMITE_VALOR = 500005;
int mu[LIMITE_VALOR];
bool primo[LIMITE_VALOR];
vector<int> numeros_primos;
int contador_multiplos[LIMITE_VALOR];
bool ativo[LIMITE_VALOR];
int lista_valores[200005];
int quantidade_n, quantidade_operacoes;
long long resposta_atual = 0;

void precomputation_muepsilon() {
    mu[1] = 1;
    for (int i = 2; i < LIMITE_VALOR; i++) {
        if (!primo[i]) {
            numeros_primos.push_back(i);
            mu[i] = -1;
        }
        for (int p : numeros_primos) {
            if (i * p >= LIMITE_VALOR) break;
            primo[i * p] = true;
            if (i % p == 0) {
                mu[i * p] = 0;
                break;
            } else {
                mu[i * p] = -mu[i];
            }
        }
    }
}

void atualizar_resposta(int valor_idx, int operacao) {
    int valor = lista_valores[valor_idx];
    // Iterar sobre todos os fatores próprios de 'valor'
    for (int f = 1; f * f <= valor; f++) {
        if (valor % f == 0) {
            int fator1 = f;
            int fator2 = valor / f;
            
            // Atualizar contribuição para o primeiro fator
            if (ativo[valor_idx]) {
                 // Removendo do conjunto: subtrai impacto anterior
                 // Nota: Lógica simplificada para demonstração
                 resposta_atual -= 1LL * (contador_multiplos[fator1] - 1) * mu[fator1];
            } else {
                 // Adicionando ao conjunto: soma novo impacto
                 resposta_atual += 1LL * contador_multiplos[fator1] * mu[fator1];
            }
            contador_multiplos[fator1] += (ativo[valor_idx] ? -1 : 1);

            if (fator1 != fator2) {
                // Repetir para o segundo fator
                if (ativo[valor_idx]) {
                     resposta_atual -= 1LL * (contador_multiplos[fator2] - 1) * mu[fator2];
                } else {
                     resposta_atual += 1LL * contador_multiplos[fator2] * mu[fator2];
                }
                contador_multiplos[fator2] += (ativo[valor_idx] ? -1 : 1);
            }
        }
    }
    ativo[valor_idx] = !ativo[valor_idx];
}

int main() {
    scanf("%d %d", &quantidade_n, &quantidade_operacoes);
    precomputation_muepsilon();

    for (int i = 1; i <= quantidade_n; i++) {
        scanf("%d", &lista_valores[i]);
    }

    while (quantidade_operacoes--) {
        int op_idx;
        scanf("%d", &op_idx);
        atualizar_resposta(op_idx);
        printf("%lld\n", resposta_atual);
    }
    return 0;
}

Tags: programação-dinâmica dijkstra inversao-de-mobius teoria-dos-numeros Grafos

Publicado em 9-21 05:25