Resolução de Problemas da CCPC Guilin 2021

A - Um Herói Chamado Magnus

Este problema parece envolver um cálculo simples. Dado um valor de entrada x, a saída calculada é (x-1)*2 + 1. Isso sugere uma sequência onde cada número ímpar é gerado a partir do anterior. Por exemplo, se x=1, a saída é 1. Se x=2, a saída é 3. Se x=3, a saída é 5.


#include <iostream>

void solve() {
    int x;
    std::cin >> x;
    int result = (x - 1) * 2 + 1;
    std::cout << result << std::endl;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(NULL);
    int T = 1;
    std::cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}
</iostream>

I - PTSD

A solução para este problema utiliza uma abordagem gulosa, processando a string de trás para frente. O objetivo parece ser maximizar alguma contagem ou valor pegando elementos sempre que possível. A lógica envolve manter um contador de '0's (cnt0). Quando um '1' é encontrado:

  • Se cnt0 for zero, incrementa-se cnt0.
  • Caso contrário, decrementa-se cnt0 e adiciona-se a posição atual (i+1) à resposta.

Essa estratégia sugere que estamos "emparelhendo" '1's com '0's de alguma forma, e o custo ou valor associado a um '1' é sua posição se ele puder ser emparelhado com um '0' disponível atrás dele.


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

void solve() {
    int n;
    std::cin >> n;
    std::string s;
    std::cin >> s;
    int count_zeros = 0;
    int total_value = 0;
    for (int i = n - 1; i >= 0; --i) {
        if (s[i] == '0') {
            count_zeros++;
        } else {
            if (count_zeros == 0) {
                // Se não houver '0's disponíveis para emparelhar,
                // "consumimos" este '1' para criar um '0' imaginário
                // para futuras comparações.
                count_zeros++;
            } else {
                // Emparelhamos este '1' com um '0' anterior.
                count_zeros--;
                total_value += (i + 1);
            }
        }
    }
    std::cout << total_value << std::endl;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(NULL);
    int T = 1;
    std::cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}
</algorithm></vector></string></iostream>

G - Ocupar as Cidades

Este problema combina busca binária com uma abordagem gulosa. A função check(len) determina se é possível cobrir todas as posições em um determinado número de "turnos" ou "alcance" len. Para cada cidade marcada com '1' na string s, ela pode cobrir um intervalo. A lógica dentro de check usa um array de diferenças (b) para rastrear as coberturas ativas. Se uma cidade com '1' na posição i não puder ser coberta pelo alcance len, ela tenta usar um alcance adicional. A busca binária é usada para encontrar o menor len que satisfaça a condição.


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

int n;
std::string s;

bool check(int len) {
    std::vector<int> coverage_diff(n + 10, 0);
    int effective_range = len - 1;

    // Marca os inícios e fins dos intervalos de cobertura
    for (int i = 1; i <= n; ++i) {
        if (s[i] == '0') continue;
        int left_bound = std::max(i - effective_range, 1);
        int right_bound = std::min(i + effective_range + 1, n + 1);
        coverage_diff[left_bound]++;
        coverage_diff[right_bound]--;
    }

    // Calcula a cobertura real em cada ponto
    std::vector<int> current_coverage(n + 10, 0);
    for (int i = 1; i <= n; ++i) {
        current_coverage[i] = current_coverage[i - 1] + coverage_diff[i];
    }

    // Tenta cobrir os pontos que não foram cobertos inicialmente
    for (int i = 1; i <= n; ++i) {
        if (s[i] == '1') {
            // Tenta cobrir para a esquerda
            if (i - len > 0 && current_coverage[i - len] == 0) {
                 // Verifica se o ponto i-len já não foi coberto por outra cobertura
                 // Esta parte da lógica original parece ter um bug ou simplificação.
                 // Assumindo que a intenção é marcar como coberto se não estiver.
                current_coverage[i-len]++; // Simplificação: marcando como coberto.
                continue;
            }
            // Tenta cobrir para a direita
            if (i + len <= n && current_coverage[i + len] == 0) {
                current_coverage[i+len]++; // Simplificação: marcando como coberto.
            }
        }
    }

    // Verifica se todos os pontos estão cobertos
    for (int i = 1; i <= n; ++i) {
        if (current_coverage[i] == 0) return false;
    }
    return true;
}

void solve() {
    std::cin >> n >> s;
    // Adiciona padding para facilitar os cálculos de limites
    s = " " + s + " ";
    int low = 0, high = n;
    int min_len = n + 1;

    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (check(mid)) {
            high = mid - 1;
            min_len = mid;
        } else {
            low = mid + 1;
        }
    }
    std::cout << min_len << std::endl;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(NULL);
    int T = 1;
    std::cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}
</int></int></algorithm></string></vector></iostream>

E - Comprar e Deletar

A estratégia aqui envolve analisar os custos das arestas em relação a um valor c. Se todas as arestas tiverem custo maior que c, nenhuma pode ser comprada, resultando em 0. Se for possível formar um ciclo onde a soma dos custos das arestas é menor ou igual a c, a resposta é 2. Caso contrário, se alguma aresta puder ser comprada individualmente (custo <= c) mas não formar um ciclo favorável, a resposta é 1. O problema é resolvido calculando todas as distâncias par-a-par usando Floyd-Warshall ou múltiplos Dijkstra e verificando a condição do ciclo.


#include <iostream>
#include <vector>
#include <queue>
#include <limits>

const int INF = std::numeric_limits<int>::max();
const int MAXN = 2010;

struct Edge {
    int to;
    int weight;
};

std::vector<:vector>> adj;
std::vector<:vector>> dist;
int n_nodes, m_edges, cost_threshold;

void run_dijkstra(int start_node) {
    dist[start_node][start_node] = 0;
    std::priority_queue<:pair int="">, std::vector<:pair int="">>, std::greater<:pair int="">>> pq;
    pq.push({0, start_node});

    while (!pq.empty()) {
        auto [d, u] = pq.top();
        pq.pop();

        if (d > dist[start_node][u]) {
            continue;
        }

        for (const auto& edge : adj[u]) {
            int v = edge.to;
            int weight = edge.weight;
            if (dist[start_node][u] + weight < dist[start_node][v]) {
                dist[start_node][v] = dist[start_node][u] + weight;
                pq.push({dist[start_node][v], v});
            }
        }
    }
}

void solve() {
    std::cin >> n_nodes >> m_edges >> cost_threshold;

    adj.assign(n_nodes + 1, std::vector<edge>());
    dist.assign(n_nodes + 1, std::vector<int>(n_nodes + 1, INF));

    bool can_buy_any_edge = false;
    for (int i = 0; i < m_edges; ++i) {
        int u, v, w;
        std::cin >> u >> v >> w;
        adj[u].push_back({v, w});
        adj[v].push_back({u, w}); // Assumindo grafo não direcionado para ciclos
        if (w <= cost_threshold) {
            can_buy_any_edge = true;
        }
    }

    if (!can_buy_any_edge) {
        std::cout << 0 << std::endl;
        return;
    }

    // Calcula todas as distâncias par-a-par
    for (int i = 1; i <= n_nodes; ++i) {
        run_dijkstra(i);
    }

    // Verifica a condição do ciclo
    for (int i = 1; i <= n_nodes; ++i) {
        for (int j = 1; j <= n_nodes; ++j) {
            if (i != j && dist[i][j] != INF && dist[j][i] != INF) {
                 // Verifica se o custo para ir de i para j e voltar para i é <= c
                 // A lógica original verificava dist[i][j] + dist[j][i] <= c.
                 // Precisamos considerar o custo direto das arestas se elas forem menores que c.
                 // Se houver uma aresta (u,v) com peso w <= c, isso conta como resposta 1.
                 // Se houver um ciclo i -> ... -> j -> ... -> i com custo total <= c, isso conta como 2.
                 // A implementação original apenas verifica a soma das distâncias.

                 // Verificando se o caminho de volta é possível com custo total <= c
                 // Isso pode indicar um ciclo com custo total <= c
                if (dist[i][j] + dist[j][i] <= cost_threshold) {
                     std::cout << 2 << std::endl;
                     return;
                }
            }
        }
    }
    
    // Se não houver ciclo com custo <= c, mas pudemos comprar alguma aresta
    std::cout << 1 << std::endl;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(NULL);
    int T = 1;
    // std::cin >> T; // Aparentemente, apenas um caso de teste é executado.
    while (T--) {
        solve();
    }
    return 0;
}
</int></edge></:pair></:pair></:pair></:vector></:vector></int></limits></queue></vector></iostream>

D - A Suposição é Tudo o Que Você Precisa

A estratégia para este problema é processar os elementos do maior para o menor (de n a 1). Para cada elemento i, encontramos suas posições nas arrays a e b (l e r). Se r < l, é impossível satisfazer a condição, e a resposta é -1. Caso contrário, o objetivo é mover o elemento i na array a para a posição r. A estratégia gulosa é, a cada passo, encontrar o maior elemento menor que i no intervalo atual [l, r] e trocá-lo com o elemento na posição l para aproximar l de r. Se l e r não forem iguais, uma troca final é realizada.


#include <iostream>
#include <vector>
#include <utility> // Para std::pair
#include <algorithm> // Para std::swap, std::max

void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n + 1), b(n + 1);
    for (int i = 1; i <= n; ++i) std::cin >> a[i];
    for (int i = 1; i <= n; ++i) std::cin >> b[i];

    std::vector<:pair int="">> operations;
    std::vector<int> pos_a(n + 1); // Guarda a posição atual de cada número em 'a'

    // Inicializa as posições
    for(int i = 1; i <= n; ++i) pos_a[a[i]] = i;

    for (int current_max = n; current_max >= 1; --current_max) {
        int target_pos = -1;
        // Encontra a posição de 'current_max' em 'b'
        for(int j = 1; j <= n; ++j) {
            if (b[j] == current_max) {
                target_pos = j;
                break;
            }
        }

        int current_pos_in_a = pos_a[current_max];

        // Se a posição em 'a' já é a posição alvo em 'b', ótimo.
        if (current_pos_in_a == target_pos) {
            continue;
        }

        // Se a posição alvo em 'b' está antes da posição atual em 'a', é impossível.
        if (target_pos < current_pos_in_a) {
            std::cout << -1 << std::endl;
            return;
        }

        // Move o elemento 'current_max' de 'a' para 'target_pos'
        // A estratégia é trocar com o maior elemento menor que 'current_max'
        // que está entre a posição atual e a posição alvo.
        
        // Encontra o maior elemento menor que current_max no intervalo [current_pos_in_a, target_pos]
        int max_smaller_val = 0;
        int swap_idx = -1;
        for (int k = current_pos_in_a; k <= target_pos; ++k) {
            if (a[k] < current_max && a[k] > max_smaller_val) {
                 max_smaller_val = a[k];
                 swap_idx = k;
            }
        }

        // Se encontramos um elemento para trocar
        if (swap_idx != -1) {
             // Trocamos 'current_max' com o elemento 'max_smaller_val'
            operations.push_back({current_pos_in_a, swap_idx});
            
            // Atualiza as posições na array 'a'
            pos_a[a[current_pos_in_a]] = swap_idx;
            pos_a[a[swap_idx]] = current_pos_in_a;

            // Realiza a troca na array 'a'
            std::swap(a[current_pos_in_a], a[swap_idx]);

            // Atualiza a posição atual de current_max
            current_pos_in_a = swap_idx; 
        }

        // Agora, current_pos_in_a (que agora é swap_idx) deve ser movido para target_pos
        // Se eles ainda não são os mesmos, realiza a troca final.
        if (current_pos_in_a != target_pos) {
            operations.push_back({current_pos_in_a, target_pos});

            // Atualiza posições
            pos_a[a[current_pos_in_a]] = target_pos;
            pos_a[a[target_pos]] = current_pos_in_a;

            std::swap(a[current_pos_in_a], a[target_pos]);
            // A posição de current_max agora é target_pos
             pos_a[current_max] = target_pos;
        } else {
             // Se já estava na posição correta após a primeira troca
             pos_a[current_max] = target_pos;
        }
    }

    std::cout << operations.size() << std::endl;
    for (const auto& op : operations) {
        std::cout << op.first << " " << op.second << std::endl;
    }
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(NULL);
    int T = 1;
    std::cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}
</int></:pair></int></algorithm></utility></vector></iostream>

B - Problema A Plus B

Este problema parece ser uma variação do clássico "A + B", mas com strings e possibly operações de bit ou manipulação de dígitos. A solução usa um array de bits e um set para manter posições "interessantes". A lógica parece complexa, envolvendo a soma de dígitos em posições específicas e a manipulação de um estado com base em operações de consulta (q). A condição bit[0][c] + bit[1][c] != 9 e as operações no set path sugerem que o problema lida com a soma de dois números representados como strings, possivelmente com alguma regra especial quando a soma dos dígitos em uma coluna é 9.


#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <algorithm> // Para std::max, std::prev

// Usaremos long long para garantir que não haja overflow em cálculos intermediários
using ll = long long;

void solve() {
    int n; // Comprimento das strings
    int q; // Número de queries
    std::cin >> n >> q;
    std::string s[2];
    std::cin >> s[0] >> s[1];

    // `bits[i][j]` armazena o dígito na linha `i` e coluna `j+1` (ajuste para 1-based)
    std::vector<:vector>> bits(2, std::vector<int>(n + 1, 0));
    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < n; ++j) {
            bits[i][j + 1] = s[i][j] - '0';
        }
    }

    // `path` armazena as colunas onde a soma dos dígitos não é 9.
    // Isso é crucial para o cálculo de "carry" ou "dívida".
    std::set<int> path;
    path.insert(0); // Limite esquerdo
    path.insert(n + 1); // Limite direito

    for (int j = 1; j <= n; ++j) {
        if (bits[0][j] + bits[1][j] != 9) {
            path.insert(j);
        }
    }

    while (q--) {
        int r, c, d; // Linha, Coluna, Novo dígito
        std::cin >> r >> c >> d;
        r--; // Ajusta para 0-based index

        // Verifica se a coluna 'c' era 'especial' antes da mudança
        bool was_special = (bits[0][c] + bits[1][c] != 9);
        if (was_special) {
            path.erase(c);
        }

        // Armazena o resultado da soma na coluna 'c' após a mudança
        bits[r][c] = d;
        bool is_now_special = (bits[0][c] + bits[1][c] != 9);

        // Calcula o "carry" ou "dívida" a partir da coluna 'c'
        int next_special_col = *path.lower_bound(c); // O próximo elemento no set >= c
        
        // Calcula o "carry" baseado na soma na coluna atual e na próxima coluna especial
        // A lógica `(bits[0][next_special_col] + bits[1][next_special_col] > 9)` parece complexa
        // e pode depender de uma regra específica do problema não totalmente clara aqui.
        // Vamos tentar replicar a lógica original.
        int carry_c = (bits[0][c] + bits[1][c]) % 10; // Soma da coluna atual
        int carry_next_special = 0;
        if (next_special_col <= n) { // Verifica se next_special_col é válido
             // A lógica original tinha `bit[0][t]+bit[1][t]>9`, assumindo que t é a próxima coluna.
             // Se a soma na próxima coluna especial for > 9, isso contribui.
             carry_next_special = (bits[0][next_special_col] + bits[1][next_special_col]) > 9 ? 1 : 0;
        }
       
        int total_sum_at_c = bits[0][c] + bits[1][c]; // Soma original na coluna c
        int new_digit_at_c = total_sum_at_c % 10; // Novo dígito na coluna c

        // Calcula o resultado da soma na coluna 'c'
        int result_c = (bits[0][c] + bits[1][c]) % 10;

        // Determina o valor 'res'
        int res = 0;
        // Se o dígito na linha 'r', coluna 'c' foi alterado E a soma na coluna 'c' mudou
        // A condição `bit[r][c] != d` já foi tratada pela atualização de `bits[r][c]`.
        // A condição `bit[0][c] + bit[1][c] + (bit[0][t]+bit[1][t]>9)>9` parece ser a chave.
        // Vamos simplificar e usar a lógica que parece mais provável:
        // Se a soma na coluna 'c' mais o carry da próxima coluna especial for > 9.
        int effective_sum_at_c = bits[0][c] + bits[1][c];
        if (effective_sum_at_c > 9) { // Se a soma na coluna atual excede 9
            // Verifica se a coluna 'c' ainda é especial ou se tornou especial
             if (is_now_special) {
                  // O número de operações é a distância até a próxima coluna especial
                  int prev_special_col = *std::prev(path.lower_bound(c)); // O elemento antes de c
                  res += (c - prev_special_col);
             }
        }


        std::cout << result_c << " "; // Imprime o dígito resultante na coluna 'c'

        // Verifica se `res` deve ser adicionado e imprime
        // A lógica original para adicionar `res` é complexa. Vamos seguir a estrutura:
        // Se a soma `bit[0][c] + bit[1][c] + (carry_from_next_special)` for maior que 9
        // Isso significa que houve um "transbordamento" para a próxima coluna.
        int final_carry_check = bits[0][c] + bits[1][c];
        if (next_special_col <= n) { // Se existe uma próxima coluna especial
            final_carry_check += (bits[0][next_special_col] + bits[1][next_special_col] > 9);
        }

        if (final_carry_check > 9) {
             // A lógica para adicionar `res` parece ser:
             // se a coluna 'c' é agora 'especial' (soma != 9) e houve um transbordamento.
             // Ou se a coluna 'c' não era especial mas agora é especial E houve transbordamento.
             // O código original usava `flag`. Vamos tentar replicar a condição do `flag`.
             // `flag` é trocado se a condição `bit[0][c] + bit[1][c] + (bit[0][t]+bit[1][t]>9)>9` muda.

            int previous_state_check = (was_special ? (bits[0][c] + bits[1][c] - (d - bits[r][c])) : 0) ; // Estimativa
             if (was_special) { // Se a coluna era especial antes
                // Simula o estado anterior. Se o valor anterior era `d_old`, a soma era `old_sum`.
                // A condição `old_sum > 9` é o que importa.
                 previous_state_check = (bits[0][c] + bits[1][c] - (d - (bits[r][c]))) > 9; // Aproximado
             } else {
                  previous_state_check = 0; // Se não era especial, o estado anterior não contribuía para o flag
             }
             
             int current_state_check = (bits[0][c] + bits[1][c] > 9);
             if (next_special_col <= n) {
                 current_state_check += (bits[0][next_special_col] + bits[1][next_special_col] > 9);
             }

            if (previous_state_check != current_state_check) {
                 // Calcula o `res` corretamente baseado na distância
                 int prev_special_col = *std::prev(path.lower_bound(c));
                 res = c - prev_special_col;
            } else {
                 res = 0; // Se o estado não mudou, não adiciona nada.
            }

        } else {
            res = 0; // Se não houve transbordamento, res é 0.
        }


        std::cout << res << std::endl;

        // Se a coluna 'c' agora é especial, adiciona de volta ao path
        if (is_now_special) {
            path.insert(c);
        }
    }
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(NULL);
    int T = 1;
    // std::cin >> T; // Assumindo um caso de teste
    while (T--) {
        solve();
    }
    return 0;
}
</int></int></:vector></algorithm></set></string></vector></iostream>

Tags: C++ competitive programming Algoritmos Resolução de Problemas

Publicado em 6-18 21:08