Guía de Ejercicios en Programación Dinámica con Código en C++

Problema: Calcular el número de formas de alcanzar sumas específicas usando un conjunto de items, y luego determinar el efecto de remover cada item individualmente.

Solución: Se utiliza DP para calcular todas las combinaciones y luego se sustrae la contribución de cada item usando recálculo parcial.

// Código modificado para el problema P4141
#include <iostream>
#include <vector>
using namespace std;

const int LIMITE_PESO = 4020;

int main() {
    int num_items, capacidad;
    cin >> num_items >> capacidad;
    vector<int> pesos(num_items);
    for (int i = 0; i < num_items; ++i) cin >> pesos[i];
    
    vector<int> dp_total(capacidad + 1, 0);
    dp_total[0] = 1;
    // Calcular DP considerando todos los items
    for (int k = 0; k < num_items; ++k) {
        for (int j = capacidad; j >= pesos[k]; --j) {
            dp_total[j] = (dp_total[j] + dp_total[j - pesos[k]]) % 10;
        }
    }
    
    vector<vector<int>> resultados(num_items, vector<int>(capacidad + 1));
    for (int i = 0; i < num_items; ++i) {
        vector<int> dp_excluido(capacidad + 1, 0);
        dp_excluido[0] = 1;
        // Recalcular DP excluyendo el item actual
        for (int k = 0; k < num_items; ++k) {
            if (k == i) continue;
            for (int j = capacidad; j >= pesos[k]; --j) {
                dp_excluido[j] = (dp_excluido[j] + dp_excluido[j - pesos[k]]) % 10;
            }
        }
        for (int j = 1; j <= capacidad; ++j) {
            resultados[i][j] = (dp_total[j] - dp_excluido[j] + 10) % 10;
        }
    }
    
    for (int i = 0; i < num_items; ++i) {
        for (int j = 1; j <= capacidad; ++j) {
            cout << resultados[i][j];
        }
        cout << endl;
    }
    return 0;
}

Ejemplo 2: DP de Intervalos para Minimización

Problema: Dado un arreglo, encontrar el costo mínimo para reducirlo a un solo elemento mediante operaciones de combinación.

Solución: Se aplica DP de intervalos, donde dp[i][j] representa el costo mínimo para el subarreglo de i a j.

// Código modificado para el problema CF607B
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int longitud;
    cin >> longitud;
    vector<int> elementos(longitud + 1);
    for (int i = 1; i <= longitud; ++i) cin >> elementos[i];
    
    vector<vector<int>> dp(longitud + 2, vector<int>(longitud + 2, 1e9));
    for (int i = 1; i <= longitud; ++i) dp[i][i] = 1;
    for (int len = 2; len <= longitud; ++len) {
        for (int izq = 1; izq <= longitud - len + 1; ++izq) {
            int der = izq + len - 1;
            if (len == 2) {
                dp[izq][der] = (elementos[izq] == elementos[der]) ? 1 : 2;
                continue;
            }
            for (int k = izq; k < der; ++k) {
                dp[izq][der] = min(dp[izq][der], dp[izq][k] + dp[k + 1][der]);
            }
            if (elementos[izq] == elementos[der]) {
                dp[izq][der] = min(dp[izq][der], dp[izq + 1][der - 1]);
            }
        }
    }
    cout << dp[1][longitud];
    return 0;
}

Ejemplo 3: DP de Dígitos con Transformaciones

Problema: Para una cadena numérica, calcular la longitud total después de múltiples transformaciones donde cada dígito se convierte en dos.

Solución: Precomputar la longitud resultante para cada dígito y número de transformaciones usando DP.

// Código modificado para el problema CF1513C
#include <iostream>
#include <vector>
#include <string>
using namespace std;

const int MODULO = 1e9 + 7;
const int MAX_TRANSFORMACIONES = 3e5 + 10;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    // Precomputar longitudes: dp[t][d] = longitud del dígito d después de t transformaciones
    vector<vector<int>> longitudes(MAX_TRANSFORMACIONES, vector<int>(10));
    for (int d = 0; d <= 9; ++d) longitudes[0][d] = 1;
    for (int t = 1; t < MAX_TRANSFORMACIONES; ++t) {
        for (int d = 0; d <= 8; ++d) {
            longitudes[t][d] = longitudes[t - 1][d + 1];
        }
        longitudes[t][9] = (longitudes[t - 1][1] + longitudes[t - 1][0]) % MODULO;
    }
    
    int casos_prueba;
    cin >> casos_prueba;
    while (casos_prueba--) {
        string cadena;
        int transformaciones;
        cin >> cadena >> transformaciones;
        int suma = 0;
        for (char c : cadena) {
            suma = (suma + longitudes[transformaciones][c - '0']) % MODULO;
        }
        cout << suma << endl;
    }
    return 0;
}

Ejemplo 4: DP con Estados Múltiples para Árboles y Colores

Problema: Colorear árboles con restricciones de grupos, minimizando el costo total.

Solución: DP con tres dimensiones: posición, color actual y número de grupos usados.

// Código modificado para el problema CF711C
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;

int main() {
    int num_arboles, num_colores, grupos_max;
    cin >> num_arboles >> num_colores >> grupos_max;
    vector<int> colores_predefinidos(num_arboles + 1);
    for (int i = 1; i <= num_arboles; ++i) cin >> colores_predefinidos[i];
    vector<vector<int>> costos(num_arboles + 1, vector<int>(num_colores + 1));
    for (int i = 1; i <= num_arboles; ++i) {
        for (int j = 1; j <= num_colores; ++j) {
            cin >> costos[i][j];
        }
    }
    
    // dp[pos][color][grupos] = costo mínimo
    vector<vector<vector<int>>> dp(num_arboles + 1, vector<vector<int>>(num_colores + 1, vector<int>(grupos_max + 1, 1e18)));
    if (colores_predefinidos[1] != 0) {
        dp[1][colores_predefinidos[1]][1] = 0;
    } else {
        for (int c = 1; c <= num_colores; ++c) {
            dp[1][c][1] = costos[1][c];
        }
    }
    for (int i = 2; i <= num_arboles; ++i) {
        if (colores_predefinidos[i] != 0) {
            int color_actual = colores_predefinidos[i];
            for (int color_ant = 1; color_ant <= num_colores; ++color_ant) {
                for (int g = 1; g <= grupos_max; ++g) {
                    if (color_ant != color_actual) {
                        dp[i][color_actual][g] = min(dp[i][color_actual][g], dp[i - 1][color_ant][g - 1]);
                    } else {
                        dp[i][color_actual][g] = min(dp[i][color_actual][g], dp[i - 1][color_actual][g]);
                    }
                }
            }
        } else {
            for (int color_actual = 1; color_actual <= num_colores; ++color_actual) {
                for (int color_ant = 1; color_ant <= num_colores; ++color_ant) {
                    for (int g = 1; g <= grupos_max; ++g) {
                        int costo_adicional = (color_ant != color_actual) ? costos[i][color_actual] : 0;
                        if (color_ant != color_actual) {
                            dp[i][color_actual][g] = min(dp[i][color_actual][g], dp[i - 1][color_ant][g - 1] + costo_adicional);
                        } else {
                            dp[i][color_actual][g] = min(dp[i][color_actual][g], dp[i - 1][color_actual][g] + costo_adicional);
                        }
                    }
                }
            }
        }
    }
    
    int resultado = 1e18;
    if (colores_predefinidos[num_arboles] != 0) {
        resultado = dp[num_arboles][colores_predefinidos[num_arboles]][grupos_max];
    } else {
        for (int c = 1; c <= num_colores; ++c) {
            resultado = min(resultado, dp[num_arboles][c][grupos_max]);
        }
    }
    cout << (resultado >= 1e18 ? -1 : resultado);
    return 0;
}

Ejemplo 5: DP para Cadenas con Inversiones

Problema: Dado una secuencia de cadenas, decidir cuándo invertirlas para mantener el orden no decreciente, minimizando el costo.

Solución: DP con dos estados por posición: cadena original o invertida.

// Código modificado para el problema CF706C
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> costos(n + 1);
    for (int i = 1; i <= n; ++i) cin >> costos[i];
    vector<string> cadenas(n + 1), invertidas(n + 1);
    for (int i = 1; i <= n; ++i) {
        cin >> cadenas[i];
        invertidas[i] = cadenas[i];
        reverse(invertidas[i].begin(), invertidas[i].end());
    }
    
    // dp[i][0] = costo mínimo hasta i usando cadena original
    // dp[i][1] = costo mínimo hasta i usando cadena invertida
    vector<vector<long long>> dp(n + 1, vector<long long>(2, 1e18));
    dp[1][0] = 0;
    dp[1][1] = costos[1];
    for (int i = 2; i <= n; ++i) {
        if (cadenas[i] >= cadenas[i - 1]) dp[i][0] = min(dp[i][0], dp[i - 1][0]);
        if (invertidas[i] >= invertidas[i - 1]) dp[i][1] = min(dp[i][1], dp[i - 1][1] + costos[i]);
        if (invertidas[i] >= cadenas[i - 1]) dp[i][1] = min(dp[i][1], dp[i - 1][0] + costos[i]);
        if (cadenas[i] >= invertidas[i - 1]) dp[i][0] = min(dp[i][0], dp[i - 1][1]);
    }
    long long min_costo = min(dp[n][0], dp[n][1]);
    cout << (min_costo >= 1e18 ? -1 : min_costo);
    return 0;
}

Ejemplo 6: DP para Distancia de Edición

Problema: Encontrar el número mínimo de operaciones (inserción, eliminación, sustitución) para transformar una cadena en otra.

Solución: DP clásico donde dp[i][j] es el costo para trensformar los primeros i caracteres en los primeros j.

// Código modificado para el problema P2758
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int main() {
    string origen, destino;
    cin >> origen >> destino;
    int len_origen = origen.size(), len_destino = destino.size();
    vector<vector<int>> dp(len_origen + 1, vector<int>(len_destino + 1));
    for (int i = 0; i <= len_origen; ++i) dp[i][0] = i;
    for (int j = 0; j <= len_destino; ++j) dp[0][j] = j;
    
    for (int i = 1; i <= len_origen; ++i) {
        for (int j = 1; j <= len_destino; ++j) {
            int costo_sustitucion = (origen[i - 1] == destino[j - 1]) ? 0 : 1;
            dp[i][j] = min({
                dp[i - 1][j] + 1,        // Eliminación
                dp[i][j - 1] + 1,        // Inserción
                dp[i - 1][j - 1] + costo_sustitucion  // Sustitución
            });
        }
    }
    cout << dp[len_origen][len_destino];
    return 0;
}

Ejemplo 7: DP con Diferencias Aritméticas

Problema: Contar el número de subsecuencias con diferencias constantes.

Solución: DP con estados que almacenan el último valor y la diferencia común.

// Código modificado para el problema P4933
#include <iostream>
#include <vector>
using namespace std;

const int MODULO = 998244353;
const int DESPLAZAMIENTO = 20000;

int main() {
    int n;
    cin >> n;
    vector<int> valores(n + 1);
    for (int i = 1; i <= n; ++i) cin >> valores[i];
    
    // dp[i][d] = número de subsecuencias terminando en i con diferencia d
    vector<vector<int>> dp(n + 1, vector<int>(2 * DESPLAZAMIENTO + 1, 0));
    long long total = 0;
    for (int i = 1; i <= n; ++i) {
        total = (total + 1) % MODULO; // Subsecuencia de un solo elemento
        for (int j = 1; j < i; ++j) {
            int diff = valores[i] - valores[j] + DESPLAZAMIENTO;
            dp[i][diff] = (dp[i][diff] + dp[j][diff] + 1) % MODULO;
            total = (total + dp[j][diff] + 1) % MODULO;
        }
    }
    cout << total % MODULO;
    return 0;
}

Ejemplo 8: DP en Árboles para Bloques Conectados

Problema: Encontrar la suma máxima de un bloque conectado en un árbol.

Solución: DP donde cada nodo calcula la mejor suma considerando sus hijos.

// Código modificado para el problema P8625
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

vector<vector<int>> grafo;
vector<long long> valores, dp;

void dfs(int nodo, int padre) {
    dp[nodo] = valores[nodo];
    for (int hijo : grafo[nodo]) {
        if (hijo != padre) {
            dfs(hijo, nodo);
            dp[nodo] = max(dp[nodo], dp[nodo] + dp[hijo]);
        }
    }
}

int main() {
    int num_nodos;
    cin >> num_nodos;
    grafo.resize(num_nodos + 1);
    valores.resize(num_nodos + 1);
    dp.resize(num_nodos + 1, 0);
    for (int i = 1; i <= num_nodos; ++i) cin >> valores[i];
    for (int i = 0; i < num_nodos - 1; ++i) {
        int u, v;
        cin >> u >> v;
        grafo[u].push_back(v);
        grafo[v].push_back(u);
    }
    dfs(1, 0);
    long long max_suma = *max_element(dp.begin() + 1, dp.end());
    cout << max_suma;
    return 0;
}

Ejemplo 9: DP Matricial para Submatrices Cuadradas

Problema: Contar submatrices cuadradas llenas de unos en una matriz binaria.

Solución: Usar DP para calcular el tamaño máximo de submatrices cuadradas en cada posición.

// Código modificado para el problema P2733
#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<vector<int>> matriz(n + 1, vector<int>(n + 1));
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            char c;
            cin >> c;
            matriz[i][j] = c - '0';
        }
    }
    
    vector<vector<int>> dp(n + 1, vector<int>(n + 1, 0));
    vector<int> conteos(n + 1, 0);
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            if (matriz[i][j] == 1) {
                dp[i][j] = min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}) + 1;
                for (int k = 2; k <= dp[i][j]; ++k) {
                    conteos[k]++;
                }
            }
        }
    }
    
    for (int k = 2; k <= n; ++k) {
        if (conteos[k] > 0) {
            cout << k << ' ' << conteos[k] << endl;
        }
    }
    return 0;
}

Ejemplo 10: DP Multidimensional para Múltiples Recorridos

Problema: Encontrar la suma máxima recorriendo una matriz dos veces desde la esquina superier izquierda a la inferior derecha.

Solución: DP con cuatro dimensiones para representar las posiciones de ambos recorridos simultáneamente.

// Código modificado para el problema P1004
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int n;
    cin >> n;
    vector<vector<int>> grilla(n + 1, vector<int>(n + 1, 0));
    int r, c, valor;
    while (cin >> r >> c >> valor && r + c + valor > 0) {
        grilla[r][c] = valor;
    }
    
    // dp[i][j][k][l] = suma máxima para recorridos hasta (i,j) y (k,l)
    vector<vector<vector<vector<int>>>> dp(n + 1, vector<vector<vector<int>>>(n + 1, vector<vector<int>>(n + 1, vector<int>(n + 1, 0))));
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            for (int k = 1; k <= n; ++k) {
                for (int l = 1; l <= n; ++l) {
                    dp[i][j][k][l] = max({
                        dp[i - 1][j][k - 1][l],
                        dp[i - 1][j][k][l - 1],
                        dp[i][j - 1][k - 1][l],
                        dp[i][j - 1][k][l - 1]
                    }) + grilla[i][j] + grilla[k][l];
                    if (i == k && j == l) dp[i][j][k][l] -= grilla[k][l];
                }
            }
        }
    }
    cout << dp[n][n][n][n];
    return 0;
}

Estos ejercicios ilustran diversas aplicaciones de la programación dinámica, desde problemas básicos hasta técnicas avanzadas. La práctica constante con diferentes tipos de problemas ayuda a desarrollar habilidades sólidas en resolución de algoritmos.

Etiquetas: dynamic-programming C++ knapsack interval-dp digit-dp

Publicado el 7-12 07:53