Soluções e Análises Técnicas: Codeforces Round 1039 (Divisão 2) - Problemas A a E1

A. Centro de Reciclagem

O problema permite uma abordagem gulosa dada a restrição de tamanho reduzido para o número de sacos. A estratégia consiste em iterativamente selecionar o saco mais pesado que ainda cabe na capacidade atual c. Ao utilizar um saco, os custos dos itens remanescentes são duplicados, simulando a penalidade de espaço acumulada. Quando nenhum saco restante satisfaz a condição de peso, cada unidade residual exige uma moeda adicional.

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void resolver() {
    int n;
    long long capacidade;
    cin >> n >> capacidade;
    
    vector<long long> pesos(n);
    vector<bool> utilizado(n, false);
    for (int i = 0; i < n; ++i) cin >> pesos[i];

    long long moedas_necessarias = 0;
    int restantes = n;

    while (true) {
        int idx_melhor = -1;
        // Identifica o maior peso viável não consumido
        for (int i = 0; i < n; ++i) {
            if (!utilizado[i] && pesos[i] <= capacidade) {
                if (idx_melhor == -1 || pesos[i] > pesos[idx_melhor]) {
                    idx_melhor = i;
                }
            }
        }

        if (idx_melhor != -1) {
            utilizado[idx_melhor] = true;
            restantes--;
            
            // Aplica a penalidade de custo aos itens não marcados
            for (int i = 0; i < n; ++i) {
                if (!utilizado[i]) pesos[i] *= 2;
            }
        } else {
            // Não há mais itens compatíveis; converte todos os restantes
            moedas_necessarias += restantes;
            break;
        }
    }
    cout << moedas_necessarias << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int qtd_testes;
    cin >> qtd_testes;
    while (qtd_testes--) resolver();
    return 0;
}

B. Processo com Fila Dupla

A construção ótima segue um padrão oscilante nas extremidades da estrutura. Como os valores nas pontas esquerda e direita diferem sistematicamente, alternar entre escolher o máximo e o mínimo preserva a propriedade solicitada pelo enunciado. A implementação utiliza dois ponteiros para percorrer o vetor, decidindo a direção baseada na paridade do turno atual.

#include <iostream>
#include <vector>

using namespace std;

void resolver() {
    int n;
    cin >> n;
    vector<int> dados(n);
    for (int i = 0; i < n; ++i) cin >> dados[i];

    int esq = 0, dir = n - 1;
    string padrao = "";

    for (int turno = 0; turno < n; ++turno) {
        bool extrair_esquerda;
        // Alternância: pares buscam maior, ímpares buscam menor
        if (turno % 2 == 0) {
            extrair_esquerda = (dados[esq] >= dados[dir]);
        } else {
            extrair_esquerda = (dados[esq] <= dados[dir]);
        }

        if (extrair_esquerda) {
            padrao += 'L';
            esq++;
        } else {
            padrao += 'R';
            dir--;
        }
    }
    cout << padrao << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int qtd_testes;
    cin >> qtd_testes;
    while (qtd_testes--) resolver();
    return 0;
}

C. Esquerda Abaixo

A validade da configuração depende esrtitamente de limites cumulativos impostas pela ordem mínima de incremento. Para cada posição, verifica-se se o alvo excede a restrição atual. Caso contrário, ajusta-se o limite com base na paridade do valor desejdao, garantindo que a progressão aritmética implícita não viole as condições anteriores.

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void resolver() {
    int n;
    cin >> n;
    vector<long long> alvo(n);
    for (int i = 0; i < n; ++i) cin >> alvo[i];

    long long limite_atual = 1e18;
    bool valido = true;

    for (int i = 0; i < n; ++i) {
        if (alvo[i] > limite_atual) {
            bool viola_limite = false;
            if (alvo[i] % 2 != 0) {
                if ((alvo[i] / 2) > limite_atual || (alvo[i] / 2 + 1) > limite_atual)
                    viola_limite = true;
            } else {
                if ((alvo[i] / 2 - 1) > limite_atual || (alvo[i] / 2 + 1) > limite_atual)
                    viola_limite = true;
            }
            if (viola_limite) {
                valido = false;
                break;
            }
        }
        limite_atual = min(limite_atual, alvo[i]);
    }
    cout << (valido ? "YES" : "NO") << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int qtd_testes;
    cin >> qtd_testes;
    while (qtd_testes--) resolver();
    return 0;
}

D. Soma da Subseção Decrescente

A restrição max(p[i-2], p[i-1]) > p[i] limita as extensões possíveis, permitindo que apenas as duas posições imediatas anteriores sejam consideradas como origem das sequências. Define-se dp[i] como o número de arranjos válidos terminados em i. As transições somam o acúmulo anterior acrescido do número de opções de início de subseção, combinando casos base e estendidos.

#include <iostream>
#include <vector>

using namespace std;

void resolver() {
    int n;
    cin >> n;
    vector<int> seq(n + 1);
    for (int i = 1; i <= n; ++i) cin >> seq[i];

    vector<long long> dp(n + 1, 0);
    long long acumulador_total = 0;

    for (int i = 1; i <= n; ++i) {
        if (i == 1) {
            dp[i] = 1;
        } else if (i == 2) {
            if (seq[i - 1] > seq[i]) dp[i] = dp[i - 1] + 2;
            else dp[i] = 2;
        } else {
            dp[i] = 0;
            // Extensão direta do índice anterior
            if (seq[i - 1] > seq[i]) dp[i] = dp[i - 1] + i;
            // Extensão pulando um índice
            if (seq[i - 2] > seq[i]) dp[i] = max(dp[i], dp[i - 2] + i);
        }
        acumulador_total += dp[i];
    }
    cout << acumulador_total << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int qtd_testes;
    cin >> qtd_testes;
    while (qtd_testes--) resolver();
    return 0;
}

E1. Submediana (Versão Fácil)

A solução combina busca binária sobre o intervalo de respostas com verificação linear. Para cada candidato médio, transforma-se o vetor em sinais positivos e negativos. Utiliza-se pré-somas combinadas com vetores de extensão máxima à esquerda e à direita para determinar se existe algum intervalo de comprimento ≥ k cuja soma seja não negativa. Após encontrar o valor ótimo, reconstrói-se a janela válida varrendo o array até a primeira ocorrência que satisfaça a condição.

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void resolver() {
    int n, k;
    cin >> n >> k;
    vector<int> elementos(n + 1);
    for (int i = 1; i <= n; ++i) cin >> elementos[i];

    auto verificar_candidato = [&](int pivô) -> pair<bool, int> {
        vector<int> ext_esq(n + 2, 0), ext_dir(n + 2, 0), prefixo(n + 2, 0);
        
        for (int i = 1; i <= n; ++i) {
            int sinal = (elementos[i] >= pivô) ? 1 : -1;
            ext_esq[i] = max(ext_esq[i - 1] + sinal, 0);
            prefixo[i] = prefixo[i - 1] + sinal;
        }
        for (int i = n; i >= 1; --i) {
            int sinal = (elementos[i] >= pivô) ? 1 : -1;
            ext_dir[i] = max(ext_dir[i + 1] + sinal, 0);
        }

        int primeira_ocorrencia = -1;
        for (int i = 1; i <= n - k + 1; ++i) {
            int j = i + k - 1;
            int soma_janela = prefixo[j] - prefixo[i - 1];
            int potencial_expansao = ext_esq[i - 1] + ext_dir[j + 1];
            
            if (soma_janela + potencial_expansao >= 0) {
                if (primeira_ocorrencia == -1) primeira_ocorrencia = i;
            }
        }
        return {primeira_ocorrencia != -1, primeira_ocorrencia};
    };

    int infimo = 1, supremo = n, resposta_final = -1, indice_inicio = -1;
    while (infimo <= supremo) {
        int meio = infimo + (supremo - infimo) / 2;
        auto [encontrou, pos] = verificar_candidato(meio);
        if (encontrou) {
            resposta_final = meio;
            indice_inicio = pos;
            infimo = meio + 1;
        } else {
            supremo = meio - 1;
        }
    }

    int lim_inf = (indice_inicio != -1) ? indice_inicio : 1;
    int lim_sup = -1;
    int soma_acumulada = 0;

    for (int i = lim_inf; i <= n; ++i) {
        soma_acumulada += (elementos[i] >= resposta_final ? 1 : -1);
        if ((i - lim_inf + 1) >= k && soma_acumulada >= 0) {
            lim_sup = i;
            break;
        }
    }
    cout << resposta_final << " " << lim_inf << " " << lim_sup << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int qtd_testes;
    cin >> qtd_testes;
    while (qtd_testes--) resolver();
    return 0;
}

Tags: codeforces-round-1039 algoritmos-gulosos filas-duplas programação-dinâmica busca-binaria

Publicado em 7-31 13:02