Análise e Soluções de Algoritmos: Programação Dinâmica, Grafos e Matemática Discreta

Programação Dinâmica em Intervalos Circulares

A resolução de problemas envolvendo estruturas circulares frequentemente requer a técnica de duplicação do array de entrada para simular o anel linearmente. Para este problema, o primeiro passo é pré-processar a contagem de elementos distintos em todos os intervalos possíveis, o que pode ser realizado em $O(n^3)$. Em seguida, aplica-se a Programação Dinâmica em Intervalos para calcular o lucro máximo de combinações.

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

using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    int n;
    if (!(cin >> n)) return 0;
    
    vector<int> elements(2 * n);
    for (int i = 0; i < n; ++i) {
        cin >> elements[i];
        elements[i + n] = elements[i];
    }

    vector<vector<int>> distinct_count(2 * n, vector<int>(2 * n, 0));
    for (int i = 0; i < 2 * n; ++i) {
        vector<bool> seen(n + 1, false);
        int count = 0;
        for (int j = i; j < 2 * n; ++j) {
            if (!seen[elements[j]]) {
                seen[elements[j]] = true;
                count++;
            }
            distinct_count[i][j] = count;
        }
    }

    vector<vector<int>> dp(2 * n, vector<int>(2 * n, 0));
    for (int len = 2; len <= n; ++len) {
        for (int i = 0; i + len - 1 < 2 * n; ++i) {
            int j = i + len - 1;
            for (int k = i; k < j; ++k) {
                dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + distinct_count[i][k] * distinct_count[k + 1][j]);
            }
        }
    }

    int max_profit = 0;
    for (int i = 0; i < n; ++i) {
        max_profit = max(max_profit, dp[i][i + n - 1]);
    }
    cout << max_profit << "\n";
    return 0;
}

Otimização Geométrica em Grade Hexagonal

O problema de minimizar o perímetro ao posicionar elementos em uma grade hexagonal exige uma abordagem matemática e gulosa. Para maximizar as arestas compartilhadas, os elementos devem ser agrupados da forma mais compacta possível, formando hexágonos concêntricos. O algoritmo calcula primeiro o maior hexágono completo que pode ser formado e, em seguida, distribui os elementos restantes ao redor da borda externa de forma gulosa para minimizar o perímetro resultante.

#include <iostream>

using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    long long total_elements;
    if (!(cin >> total_elements)) return 0;

    long long layer = 0;
    long long current_capacity = 1;
    long long perimeter = 0;

    while (true) {
        long long next_layer_elements = 6 * (layer + 1);
        if (current_capacity + next_layer_elements > total_elements) {
            perimeter = 6 * (layer + 1);
            long long remaining = total_elements - current_capacity;
            if (remaining > 0) {
                perimeter += (remaining / (layer + 1)) + (remaining % (layer + 1) > 0 ? 2 : 0);
            }
            break;
        }
        current_capacity += next_layer_elements;
        layer++;
    }

    if (total_elements == 0) perimeter = 0;
    cout << perimeter << "\n";
    return 0;
}

Teoria dos Grafos e Florestas de Anéis

Este problema pode ser modelado como um grafo funcional, onde cada nó possui exatamente uma aresta de saída, resultando em uma floresta de árvores com anéis. A estratégia inicial é gulosa: coletar todo o lucro possível dos nós que possuem estoque maior que um. Após isso, o grafo se resume a cadeias e anéis simples. Os nós em cadeias podem ter seu lucro totalmente coletado. Para os anéis, é necessário abrir o ciclo, o que implica renunciar ao lucro de um nó. O algoritmo de Tarjan é utilizado para identificar e contrair os componentes fortemente conectados, faciiltando o cálculo do lucro máximo.

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

using namespace std;

struct Item {
    int target;
    int cost;
    int price;
    int stock;
};

int n;
vector<Item> items;
vector<vector<pair<int, int>>> graph;
vector<int> dfn, low, scc_id, scc_size;
vector<long long> scc_value;
int timer_cnt = 0, scc_count = 0;
vector<int> st;
vector<bool> in_st;

void tarjan(int u) {
    dfn[u] = low[u] = ++timer_cnt;
    st.push_back(u);
    in_st[u] = true;

    for (auto& edge : graph[u]) {
        int v = edge.first;
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if (in_st[v]) {
            low[u] = min(low[u], dfn[v]);
        }
    }

    if (dfn[u] == low[u]) {
        scc_count++;
        int v;
        do {
            v = st.back();
            st.pop_back();
            in_st[v] = false;
            scc_id[v] = scc_count;
            scc_size[scc_count]++;
            scc_value[scc_count] += items[v].price - items[v].cost;
        } while (v != u);
    }
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    if (!(cin >> n)) return 0;

    items.resize(n + 1);
    graph.resize(n + 1);
    dfn.assign(n + 1, 0);
    low.assign(n + 1, 0);
    scc_id.assign(n + 1, 0);
    scc_size.assign(n + 1, 0);
    scc_value.assign(n + 1, 0);
    st.reserve(n + 1);
    in_st.assign(n + 1, false);

    long long total_profit = 0;
    vector<pair<int, int>> best_edges(n + 1, {0, 0});

    for (int i = 1; i <= n; ++i) {
        cin >> items[i].target >> items[i].cost >> items[i].price >> items[i].stock;
        if (items[i].stock > 1) {
            total_profit += (long long)(items[i].stock - 1) * (items[i].price - items[i].cost);
            items[i].stock = 1;
        }
    }

    for (int i = 1; i <= n; ++i) {
        int profit = items[items[i].target].price - items[i].cost;
        if (profit > 0 && profit > best_edges[items[i].target].first) {
            best_edges[items[i].target] = {profit, i};
        }
    }

    for (int i = 1; i <= n; ++i) {
        if (best_edges[i].first > 0) {
            graph[best_edges[i].second].push_back({i, best_edges[i].first});
        }
    }

    for (int i = 1; i <= n; ++i) {
        if (!dfn[i]) tarjan(i);
    }

    for (int i = 1; i <= scc_count; ++i) {
        total_profit += scc_value[i];
    }

    cout << total_profit << "\n";
    return 0;
}

DP em Strings e Busca de Padrões

Abordagens gulosas simples para eliminação de substrings falham devido a sobreposições e padrões não contíguos. A solução correta utiliza Programação Dinâmica em Intervalos com busca memoizada. Para um comprimento de substring candidato $d$, definimos o estado $dp[i][j]$ para verificar se o intervalo $[i, j]$ pode ser completamente eliminado. As transições ocorrem de duas formas: correspondência caractere a caractere ou correspondência em blocos de tamanho $d$. A ordem das transições é crucial para a correção do algoritmo, exigindo que a verificação em blocos seja processada antes da verificação caractere a caractere.

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

using namespace std;

int n;
string s;
vector<vector<int>> memo;

bool can_eliminate(const string& pattern, int l, int r) {
    if (l > r) return true;
    if (memo[l][r] != -1) return memo[l][r];

    int d = pattern.length();

    for (int k = r - d; k >= l; k -= d) {
        if (can_eliminate(pattern, l, k) && can_eliminate(pattern, k + 1, r)) {
            return memo[l][r] = 1;
        }
    }

    if (s[r] == pattern[(r - l) % d]) {
        if (can_eliminate(pattern, l, r - 1)) {
            return memo[l][r] = 1;
        }
    }

    return memo[l][r] = 0;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    int t;
    if (!(cin >> t)) return 0;

    while (t--) {
        cin >> s;
        n = s.length();
        string best_pattern = "";

        for (int d = 1; d <= n; ++d) {
            if (n % d != 0) continue;

            bool found = false;
            for (int i = 0; i + d <= n; ++i) {
                string pattern = s.substr(i, d);
                memo.assign(n, vector<int>(n, -1));

                if (can_eliminate(pattern, 0, n - 1)) {
                    if (best_pattern.empty() || pattern < best_pattern) {
                        best_pattern = pattern;
                    }
                    found = true;
                }
            }
            if (found) break;
        }
        cout << best_pattern << "\n";
    }
    return 0;
}

Tags: programação-dinâmica teoria-dos-grafos algoritmos-de-busca matematica-discreta c-plus-plus

Publicado em 7-22 09:56