Soluções para os Problemas D e E do Codeforces Round 2013

Problema D

Enunciado: Dada uma sequência de inteiros, é permitido realizar operações ilimitadas em que se decrementa o elemento mais à esquerda em 1 e se incremetna o elemento mais à direita em 1. O objetivo é minimizar a diferença entre o valor máximo e mínimo da sequência após as operações.

Solução: A solução ótima pode ser encontrada de forma direta sem simular o processo de redistribuição. Para determinar o valor mínimo possível, considera-se que o valor no índice i pode ser a média dos primeiros i elementos (se for o ponto mais baixo). O valor mínimo global é o máximo dessas médias calculadas para cada i. Analogamente, para o valor máximo, inverte-se a perspectiva: o valor no índice i pode ser a média dos elementos a partir de i até o final, e o valor máximo global é o mínimo dessas médias. A diferença entre o valor máximo e mínimo obtidos é a resposta.

Implementação em C++:

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

long long resolver(vector<long long>& nums) {
    int tamanho = nums.size();
    vector<long long> soma_acumulada(tamanho + 1, 0);
    for (int i = 1; i <= tamanho; ++i) {
        soma_acumulada[i] = soma_acumulada[i - 1] + nums[i - 1];
    }
    long long valor_minimo = 1e18;
    long long valor_maximo = 0;
    for (int i = 1; i <= tamanho; ++i) {
        long long media_prefixo = soma_acumulada[i] / i;
        long long media_sufixo = (soma_acumulada[tamanho] - soma_acumulada[i - 1] + tamanho - i) / (tamanho - i + 1);
        valor_minimo = min(valor_minimo, media_prefixo);
        valor_maximo = max(valor_maximo, media_sufixo);
    }
    return valor_maximo - valor_minimo;
}

int main() {
    int casos;
    cin >> casos;
    while (casos--) {
        int n;
        cin >> n;
        vector<long long> sequencia(n);
        for (int i = 0; i < n; ++i) {
            cin >> sequencia[i];
        }
        cout << resolver(sequencia) << endl;
    }
    return 0;
}

Problema E

Enunciado: Dado um array de inteiros, é possível reordená-lo livremente. O objetivo é minimizar a soma dos máximos divisores comuns (MDC) dos prefixos, ou seja, \(\sum_{i=1}^{n} \gcd(a_1, a_2, \ldots, a_i)\).

Solução: A estratégia ótima é um algoritmo guloso baseado em uma propriedade do MDC. Inicialmente, coloca-se o menor elemento como primeiro. Em seguida, repetidamente, escolhe-se o próximo elemento que minimiza o MDC acumulado. Quando o MDC atinge seu valor mínimo global, todos os elementos restantes podem ser adicionados sem alterar o MDC. A eficiência é garantida porque o MDC diminui pelo menos pela metade a cada passo significativo, requerendo no máximo \(O(\log n)\) iterações.

Implemantação em C++:

#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

long long calcular_minimo_soma(vector<long long>& elementos) {
    sort(elementos.begin(), elementos.end());
    if (elementos.size() == 1) return elementos[0];
    long long mdc_global = elementos[0];
    for (long long num : elementos) {
        mdc_global = gcd(mdc_global, num);
    }
    long long soma_total = mdc_global * elementos.size();
    soma_total += elementos[0] - mdc_global;
    long long mdc_atual = elementos[0];
    while (true) {
        long long proximo_mdc = 1e18;
        for (long long num : elementos) {
            proximo_mdc = min(proximo_mdc, gcd(mdc_atual, num));
        }
        mdc_atual = proximo_mdc;
        soma_total += mdc_atual - mdc_global;
        if (mdc_atual == mdc_global) break;
    }
    return soma_total;
}

int main() {
    int tamanho;
    cin >> tamanho;
    vector<long long> arr(tamanho);
    for (int i = 0; i < tamanho; ++i) {
        cin >> arr[i];
    }
    cout << calcular_minimo_soma(arr) << endl;
    return 0;
}

Problema CF1614D

Enunciado: Semelhante ao Problema E, mas agora o objetivo é maximizar a soma dos MDC dos prefixos. A reordenação livre é permitida.

Solução: Esta variação não pode ser resolvida por guloso. Em vez disso, utiliza-se programação dinâmica com complexidade harmônica. Define-se cnt[i] como o número de elementos que são múltiplos de i, e dp[i] como a soma máxima dos MDC para subsequências com MDC igual a i. A transição é dp[i] = max(dp[j] + (cnt[i] - cnt[j]) * i), para j sendo múltiplos de i. O caso base é dp[i] = cnt[i] * i. A resposta é dp[1].

Implementação em C++ (versão easy):

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

long long resolver_maximo(vector<int>& dados, int max_valor) {
    vector<int> contagem(max_valor + 1, 0);
    for (int num : dados) contagem[num]++;
    vector<long long> cnt(max_valor + 1, 0);
    vector<long long> dp(max_valor + 1, 0);
    for (int i = max_valor; i >= 1; --i) {
        for (int j = i; j <= max_valor; j += i) {
            cnt[i] += contagem[j];
        }
        dp[i] = static_cast<long long>(cnt[i]) * i;
        for (int j = 2 * i; j <= max_valor; j += i) {
            dp[i] = max(dp[i], dp[j] + (cnt[i] - cnt[j]) * i);
        }
    }
    return dp[1];
}

int main() {
    int n;
    cin >> n;
    vector<int> entrada(n);
    int maximo = 0;
    for (int i = 0; i < n; ++i) {
        cin >> entrada[i];
        maximo = max(maximo, entrada[i]);
    }
    cout << resolver_maximo(entrada, maximo) << endl;
    return 0;
}

Tags: Codeforces C++ algoritmos-gulosos programação-dinâmica maximo-divisor-comum

Publicado em 6-27 16:59