Análise Algorítmica e Implementações: Competição Nacional de Informática 2011

Problema 1: Sobreposição de Retângulos

A resolução baseia-se em uma simulação direta com iteração reversa. Como os tapetes são posicionados sequencialmente, aquele que cobre o ponto de consulta e posui o maior índice será o visível. Armazenamos as coordenadas e dimensões de cada retângulo e percorremos a estrutura de trás para frente, verificando a condição de inclusão do ponto alvo.

#include <iostream>
#include <vector>
struct Rectangle { int left, bottom, width, height; };
int main() {
    int n; std::cin >> n;
    std::vector<Rectangle> carpets(n);
    for (auto &c : carpets) std::cin >> c.left >> c.bottom >> c.width >> c.height;
    int tx, ty; std::cin >> tx >> ty;
    for (int i = n - 1; i >= 0; --i) {
        if (tx >= carpets[i].left && tx <= carpets[i].left + carpets[i].width &&
            ty >= carpets[i].bottom && ty <= carpets[i].bottom + carpets[i].height) {
            std::cout << i + 1 << '\n';
            return 0;
        }
    }
    std::cout << -1 << '\n';
    return 0;
}

Problema 2: Seleção de Hospedarias

O objetivo é identificar pares de estabelecimentos com a mesma classificação cromática onde exista pelo menos uma opção com custo inferior ou igual a um limite definido no intervalo entre eles. A abordagem otimizada utiliza somas de prefixo para validar a condição de preço em tempo constante. Agrupamos os índices por cor e aplicamos uma varredura linear para acumular as combinações válidas, eliminando a necessidade de estruturas de consulta de intervalo complexas.

#include <iostream>
#include <vector>
using namespace std;
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    int n, k, p; cin >> n >> k >> p;
    vector<int> color(n + 1), price(n + 1);
    vector<vector<int>> pos(k);
    vector<int> cheap_prefix(n + 1, 0);
    for (int i = 1; i <= n; ++i) {
        cin >> color[i] >> price[i];
        cheap_prefix[i] = cheap_prefix[i - 1] + (price[i] <= p ? 1 : 0);
        pos[color[i]].push_back(i);
    }
    long long total_pairs = 0;
    for (int c = 0; c < k; ++c) {
        if (pos[c].size() < 2) continue;
        int left_idx = 0;
        for (int right_idx = 1; right_idx < pos[c].size(); ++right_idx) {
            int l = pos[c][left_idx], r = pos[c][right_idx];
            if (cheap_prefix[r] - cheap_prefix[l] > 0) {
                total_pairs += (long long)(pos[c].size() - right_idx) * (right_idx - left_idx);
                left_idx = right_idx;
            }
        }
    }
    cout << total_pairs << '\n';
    return 0;
}

Problema 3: Quebra-Cabeça Mayan

Este desafio exige busca com retrocesso (backtracking) combinada com simulação de regras de gravidade e eliminação de blocos. A cada movimento lateral válido, o estado do tabuleiro é atualizado: blocos caem para preencher espaços vazios e sequências de três ou mais peças alinhadas são removidas iterativamente. O algoritmo explora a árvore de decisões até a profundidade máxima permitida, restaurando o estado anterior a cada retorno recursivo.

#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
int steps_limit;
int board[6][8];
int history[6][3];
int backup[6][6][8];
bool eliminated[6][8];

void apply_gravity() {
    for (int col = 1; col <= 5; ++col) {
        int write_pos = 1;
        for (int row = 1; row <= 7; ++row) {
            if (board[col][row] != 0) board[col][write_pos++] = board[col][row];
        }
        while (write_pos <= 7) board[col][write_pos++] = 0;
    }
}

bool clear_matches() {
    memset(eliminated, 0, sizeof(eliminated));
    bool found = false;
    for (int i = 1; i <= 5; ++i) {
        for (int j = 1; j <= 7; ++j) {
            if (!board[i][j]) continue;
            if (i > 1 && i < 5 && board[i][j] == board[i-1][j] && board[i][j] == board[i+1][j]) {
                eliminated[i-1][j] = eliminated[i][j] = eliminated[i+1][j] = true; found = true;
            }
            if (j > 1 && j < 7 && board[i][j] == board[i][j-1] && board[i][j] == board[i][j+1]) {
                eliminated[i][j-1] = eliminated[i][j] = eliminated[i][j+1] = true; found = true;
            }
        }
    }
    if (!found) return false;
    for (int i = 1; i <= 5; ++i)
        for (int j = 1; j <= 7; ++j)
            if (eliminated[i][j]) board[i][j] = 0;
    return true;
}

void save_state(int depth) {
    for(int i=1;i<=5;++i) for(int j=1;j<=7;++j) backup[depth][i][j] = board[i][j];
}
void restore_state(int depth) {
    for(int i=1;i<=5;++i) for(int j=1;j<=7;++j) board[i][j] = backup[depth][i][j];
}

void dfs(int depth) {
    bool empty = true;
    for(int i=1;i<=5;++i) if(board[i][1]) { empty = false; break; }
    if(empty) {
        for(int i=1;i<=steps_limit;++i) cout << history[i][0] << " " << history[i][1] << " " << history[i][2] << "\n";
        exit(0);
    }
    if(depth > steps_limit) return;
    save_state(depth);
    for(int i=1;i<=5;++i) {
        for(int j=1;j<=7;++j) {
            if(!board[i][j]) continue;
            if(i < 5 && board[i][j] != board[i+1][j]) {
                swap(board[i][j], board[i+1][j]);
                apply_gravity(); while(clear_matches()) apply_gravity();
                history[depth][0]=i-1; history[depth][1]=j-1; history[depth][2]=1;
                dfs(depth+1); restore_state(depth);
            }
            if(i > 1 && board[i-1][j] == 0) {
                swap(board[i][j], board[i-1][j]);
                apply_gravity(); while(clear_matches()) apply_gravity();
                history[depth][0]=i-1; history[depth][1]=j-1; history[depth][2]=-1;
                dfs(depth+1); restore_state(depth);
            }
        }
    }
}
int main() {
    cin >> steps_limit;
    for(int i=1;i<=5;++i) {
        for(int j=1;j<=8;++j) {
            int val; cin >> val;
            if(val==0) break;
            board[i][j] = val;
        }
    }
    memset(history, -1, sizeof(history));
    dfs(1);
    cout << -1 << "\n";
    return 0;
}

Problema 4: Cálculo de Coeficientes Polinomiais

A expansão de $(ax + by)^k$ segue diretamente o Teorema Binomial. O coeficiente do termo $x^n y^m$ é determinado por $\binom{k}{n} \cdot a^n \cdot b^m$. Para evitar overflow e atender aos requisitos do problema, todas as operações são realizadas sob aritmética modular. O triângulo de Pascal é pré-computado para obter as combinações, enquanto a exponenciação rápida calcula as potências das bases em tempo logarítmico.

#include <iostream>
using namespace std;
const int MOD = 10007;
long long mod_pow(long long base, long long exp) {
    long long res = 1;
    base %= MOD;
    while (exp > 0) {
        if (exp & 1) res = (res * base) % MOD;
        base = (base * base) % MOD;
        exp >>= 1;
    }
    return res;
}
int main() {
    long long a, b, k, n, m;
    cin >> a >> b >> k >> n >> m;
    long long C[1005][1005] = {0};
    C[0][0] = 1;
    for (int i = 1; i <= k; ++i) {
        C[i][0] = 1;
        for (int j = 1; j <= i; ++j) {
            C[i][j] = (C[i-1][j-1] + C[i-1][j]) % MOD;
        }
    }
    long long ans = C[k][n];
    ans = (ans * mod_pow(a, n)) % MOD;
    ans = (ans * mod_pow(b, m)) % MOD;
    cout << ans << "\n";
    return 0;
}

Problema 5: Inspeção de Qualidade

A métrica de avaliação $Y$ apresenta comportamento monotônico decrescente em relação ao parâmetro de limite $W$. Essa propriedade permite a aplicação de busca binária sobre o domínio de $W$. Para cada valor testado, arrays de soma de prefixo acumulam a quentidade e o valor total dos itens que satisfazem a condição de peso, permitindo o cálculo da métrica em tempo linear. O algoritmo rastreia a menor diferença absoluta entre $Y$ e o valor alvo $S$.

#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
struct Item { int weight, value; };
struct Range { int l, r; };
int n, m;
long long target_S;
vector<Item> items;
vector<Range> queries;
vector<int> cnt_pref, val_pref;

long long evaluate(int threshold) {
    cnt_pref.assign(n + 1, 0);
    val_pref.assign(n + 1, 0);
    for (int i = 1; i <= n; ++i) {
        cnt_pref[i] = cnt_pref[i-1] + (items[i].weight >= threshold ? 1 : 0);
        val_pref[i] = val_pref[i-1] + (items[i].weight >= threshold ? items[i].value : 0);
    }
    long long current_Y = 0;
    for (const auto& q : queries) {
        long long c = cnt_pref[q.r] - cnt_pref[q.l - 1];
        long long v = val_pref[q.r] - val_pref[q.l - 1];
        current_Y += c * v;
    }
    return current_Y;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    cin >> n >> m >> target_S;
    items.resize(n + 1);
    int min_w = 1e9, max_w = 0;
    for (int i = 1; i <= n; ++i) {
        cin >> items[i].weight >> items[i].value;
        min_w = min(min_w, items[i].weight);
        max_w = max(max_w, items[i].weight);
    }
    queries.resize(m);
    for (int i = 0; i < m; ++i) cin >> queries[i].l >> queries[i].r;

    int low = min_w - 1, high = max_w + 2;
    long long best_diff = -1;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        long long Y = evaluate(mid);
        long long diff = abs(Y - target_S);
        if (best_diff == -1 || diff < best_diff) best_diff = diff;
        if (Y > target_S) low = mid + 1;
        else high = mid - 1;
    }
    cout << best_diff << "\n";
    return 0;
}

Problema 6: Otimização de Transporte Turístico

A minimização do tempo total de viagem é alcançada atrravés de uma estratégia gulosa iterativa. Cada acelerador disponível deve ser alocado no segmento de rota onde a redução de uma unidade de tempo beneficia o maior número de passageiros. O algoritmo calcula o alcance de influência de cada trecho, considerando que o veículo não pode partir antes da chegada do último passageiro em cada parada. Após cada alocação, os horários de chegada são recalculados e o processo se repete até o esgotamento dos recursos.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Passenger { int arrival, start, end; };
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    int n, m, k;
    cin >> n >> m >> k;
    vector<int> dist(n);
    for (int i = 1; i < n; ++i) cin >> dist[i];

    vector<Passenger> pass(m);
    vector<int> last_arrival(n + 1, 0);
    vector<int> drop_count(n + 1, 0);
    for (int i = 0; i < m; ++i) {
        cin >> pass[i].arrival >> pass[i].start >> pass[i].end;
        last_arrival[pass[i].start] = max(last_arrival[pass[i].start], pass[i].arrival);
        drop_count[pass[i].end]++;
    }

    vector<int> prefix_pass(n + 1, 0);
    for (int i = 1; i <= n; ++i) prefix_pass[i] = prefix_pass[i-1] + drop_count[i];

    vector<int> bus_arrival(n + 1, 0);
    for (int i = 2; i <= n; ++i) {
        bus_arrival[i] = max(bus_arrival[i-1], last_arrival[i-1]) + dist[i-1];
    }

    long long total_time = 0;
    for (const auto& p : pass) total_time += bus_arrival[p.end] - p.arrival;

    while (k-- > 0) {
        vector<int> influence_limit(n + 1);
        influence_limit[n] = n;
        for (int i = n - 1; i >= 1; --i) {
            influence_limit[i] = (bus_arrival[i+1] <= last_arrival[i+1]) ? i + 1 : influence_limit[i+1];
        }
        int best_seg = 0, max_saved = 0;
        for (int i = 1; i < n; ++i) {
            if (dist[i] > 0) {
                int saved = prefix_pass[influence_limit[i]] - prefix_pass[i];
                if (saved > max_saved) {
                    max_saved = saved;
                    best_seg = i;
                }
            }
        }
        if (max_saved == 0) break;
        dist[best_seg]--;
        total_time -= max_saved;
        for (int i = 2; i <= n; ++i) {
            bus_arrival[i] = max(bus_arrival[i-1], last_arrival[i-1]) + dist[i-1];
        }
    }
    cout << total_time << "\n";
    return 0;
}

Tags: Algoritmos backtracking busca-binaria teorema-binomial somas-de-prefixo

Publicado em 8-19 06:26