Soluciones de problemas de programación competitiva: Teoría de grafos, Josephus, Álgebra y LCIS

Modelamos cada arma como una arista que conecta dos nodos (atributos). Así, el problema se reduce a analizar componentes conexas en un grafo.

Si la componente conexa forma un árbol (es decir, teine exactamente n-1 aristas para n nodos), no es posible seleccionar todos los nodos. En este caso, basta con no elegir el nodo de mayor valor.

Si la componente no es un árbol (tiene un ciclo), entonces cada arma puede asignarse a uno de sus dos extremos, lo que permite cubrir todos los nodos de la componente.

Problema 2: Problema de Josephus optimizado

Tenemos n personas en círculo y cada m-ésima persona es eliminada. La fórmula clásica del problema de Josephus funciona en O(n):

resultado = 0
para i desde 2 hasta n:
    resultado = (resultado + m) % i
respuesta = resultado + 1

Cuando n llega hasta 10⁹, este enfoque O(n) no es viable. La clave de la optimización radica en que, conforme i crece, el módulo frecuentemente no altera el valor acumulado. Por lo tanto, podemos agrupar múltiples iteraciones consecutivas en una sola operación de multiplicación.

Específicamente, cuando (resultado + m) < i, sabemos que el módulo no tiene efecto. Podemos calcular cuántas iteraciones consecutivas se beneficiarán de esto y procesarlas en bloque:

#include <cstdio>
#include <algorithm>
using namespace std;

int main() {
    int casos;
    scanf("%d", &casos);
    while (casos--) {
        int totalPersonas, paso;
        scanf("%d %d", &totalPersonas, &paso);
        int sobreviviente = 0;
        int bloque = 1;
        int actual = 2;
        while (actual <= totalPersonas) {
            int margen = (actual - sobreviviente) / paso;
            int restante = totalPersonas - actual + 1;
            bloque = max(1, min(restante, margen));
            sobreviviente = (sobreviviente + paso * bloque) % (actual + bloque - 1);
            actual += bloque;
        }
        printf("%d\n", sobreviviente + 1);
    }
    return 0;
}

Probleam 3: Cálculo algebraico con Fenwick Tree

Partimos de una expresión que involucra productos cruzados entre pares de elementos. Al expandir la suma sobre todo el rango [l, r] para ambos índices i y j, obtenemos exactamente el doble de la expresión original:

La expresión original equivale a:

(Σ xᵢ²) × (Σ yⱼ²) − (Σ xᵢyᵢ)²

Esto permite descomponer el cálculo en tres sumatorios independientes sobre el intervalo consultado:

  • S₁ = Σ xᵢ²
  • S₂ = Σ yᵢ²
  • S₃ = Σ xᵢ·yᵢ

El resultado final es S₁ × S₂ − S₃². Cada uno de estos tres sumatorios puede mantenerse eficientemente con un Fenwick Tree (BIT), soportando actualizaciones por punto y consultas por rango, ambas en O(log n).

Problema 4: Subsecuencia creciente común más larga (LCIS)

Definimos dp[i][j] como la longitud máxima de una subsecuencia creciente que es común a los primeros i elementos del arreglo A y los primeros j elementos del arreglo B, terminando obligatoriamente en B[j].

Transiciones:

  • Si A[i] ≠ B[j]: dp[i][j] = dp[i−1][j]
  • Si A[i] == B[j]: dp[i][j] = max(dp[i−1][k] + 1) para todo 0 ≤ k < j donde B[k] < B[j]

La complejidad naive es O(n³). La optimización consiste en observar que, durante el recorrido de j, el valor de A[i] permanece constante. Por tanto, podemos mantener un único punto de decisión óptimo (el mejor k hasta el momento) mientras iteramos sobre j, evitando el bucle interno sobre k. Esto reduce la complejidad a O(n²).

Para la reconstrucción de la secuencia, es indispensable usar una tabla bidimensional que registre el predecesor de cada estado. Usar un arreglo unidimensional provoca errores sutiles debido a que las transiciones dependen tanto de i como de j.

#include <cstdio>
#include <cstring>
using namespace std;

const int LIM = 5005;
int lenA, arrA[LIM];
int lenB, arrB[LIM];
int dp[LIM][LIM];
int predecesor[LIM][LIM];
int pila[LIM], tope;

void reconstruir(int i, int j) {
    if (i == 0 || j == 0) return;
    if (predecesor[i][j] != j) pila[++tope] = arrB[j];
    reconstruir(i - 1, predecesor[i][j]);
}

int main() {
    scanf("%d", &lenA);
    for (int i = 1; i <= lenA; ++i) scanf("%d", &arrA[i]);
    scanf("%d", &lenB);
    for (int i = 1; i <= lenB; ++i) scanf("%d", &arrB[i]);

    for (int i = 1; i <= lenA; ++i) {
        int mejorVal = dp[i - 1][0];
        int mejorPos = 0;
        for (int j = 1; j <= lenB; ++j) {
            if (arrA[i] != arrB[j]) {
                dp[i][j] = dp[i - 1][j];
                predecesor[i][j] = j;
            } else {
                dp[i][j] = mejorVal + 1;
                predecesor[i][j] = mejorPos;
            }
            if (arrB[j] < arrA[i] && dp[i - 1][j] > mejorVal) {
                mejorVal = dp[i - 1][j];
                mejorPos = j;
            }
        }
    }

    int mejorJ = 0;
    for (int j = 1; j <= lenB; ++j) {
        if (dp[lenA][j] > dp[lenA][mejorJ]) mejorJ = j;
    }

    printf("%d\n", dp[lenA][mejorJ]);
    reconstruir(lenA, mejorJ);
    for (int i = tope; i >= 1; --i) printf("%d ", pila[i]);
    return 0;
}

Etiquetas: Fenwick Tree Josephus Problem LCIS programación dinámica Teoría de Grafos

Publicado el 6-28 02:51