Análisis Algorítmico y Resolución de la Simulación MX-S

Problema 1: Optimización de Posicionamiento de Postes

En este problema, se nos presentan $n$ postes ubicados en una línea numérica en posiciones $p_i$. Cada poste tiene una altura $h_i$. Un poste puede permanecer vertical (ocupando solo el punto $p_i$), caer hacia la izquierda (ocupando el intervalo $[p_i - h_i, p_i]$) o caer hacia la derecha (ocupando $[p_i, p_i + h_i]$). El objetivo es maximizar la cantidad de postes derribados sin que sus intervalos de ocupación se traslapen.

Para resolverlo, empleamos un enfoque de programación dinámica. Definimos un estado $f[i][state]$, donde $i$ representa el poste actual y $state \in \{0, 1, 2\}$ indica si el poste está vertical, caído a la izquierda o caído a la derecha, respectivamente.

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

using namespace std;

const long long INF_LL = 2e18;

struct Elemento {
    long long pos, alt;
    bool operator<(const Elemento& otro) const {
        return pos < otro.pos;
    }
};

int main() {
    int total;
    cin >> total;
    vector<Elemento> lista(total + 1);
    for (int i = 1; i <= total; i++) {
        cin >> lista[i].pos >> lista[i].alt;
    }
    sort(lista.begin() + 1, lista.end());

    lista[0].pos = -INF_LL;
    vector<vector<int>> memo(total + 1, vector<int>(3, 0));

    for (int i = 1; i <= total; i++) {
        // Caso 0: Vertical
        memo[i][0] = max(memo[i-1][0], memo[i-1][1]);
        if (lista[i].pos - lista[i-1].pos > lista[i-1].alt) {
            memo[i][0] = max(memo[i][0], memo[i-1][2]);
        }

        // Caso 1: Izquierda
        if (lista[i].pos - lista[i-1].pos > lista[i].alt) {
            memo[i][1] = max(memo[i-1][0], memo[i-1][1]) + 1;
        }
        if (lista[i].pos - lista[i-1].pos > lista[i].alt + lista[i-1].alt) {
            memo[i][1] = max(memo[i][1], memo[i-1][2] + 1);
        }

        // Caso 2: Derecha
        memo[i][2] = max(memo[i-1][0], memo[i-1][1]) + 1;
        if (lista[i].pos - lista[i-1].pos > lista[i-1].alt) {
            memo[i][2] = max(memo[i][2], memo[i-1][2] + 1);
        }
    }

    cout << max({memo[total][0], memo[total][1], memo[total][2]}) << endl;
    return 0;
}

Problema 2: Emparejamiento Perfecto en Grafo Bipartito Completo

Se define un grafo bipartito con $n+1$ nodos en cada conujnto. Los pesos de los nodos izquierdos son $a_i$ y los de los derechos $b_i$. El peso de una arista $(i, j)$ es $\max(0, b_j - a_i)$. Dado que $a_{n+1} = +\infty$, cualquier arista conectada a este nodo tendrá un peso efectivo de 0 en términos de contribución al valor del emparejamiento (si buscamos minimizar el máximo). Debemos determinar, para cada $i$, el peso mínimo de un emparejamiento perfecto si $a_{n+1}$ se empareja con $b_i$.

La estrategia consiste en ordenar los pesos y utilizar una estructura de datos (como un multiset) para gestionar los valores máximos de forma dinámica a medida que iteramos sobre las posibles conexiones del nodo infinito.

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

using namespace std;

struct Punto {
    int valor, idx;
    bool operator<(const Punto& p) const { return valor < p.valor; }
};

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    int n;
    cin >> n;
    vector<Punto> b(n + 2);
    vector<int> a(n + 1);
    for (int i = 1; i <= n + 1; i++) { cin >> b[i].valor; b[i].idx = i; }
    for (int i = 1; i <= n; i++) cin >> a[i];

    sort(a.begin() + 1, a.end());
    sort(b.begin() + 1, b.end());

    multiset<int> diferencias;
    for (int i = 1; i <= n; i++) {
        diferencias.insert(max(0, b[i+1].valor - a[i]));
    }
    
    vector<int> res(n + 2);
    res[b[1].idx] = *diferencias.rbegin();
    
    int max_acumulado = 0;
    for (int i = 2; i <= n + 1; i++) {
        auto it = diferencias.find(max(0, b[i].valor - a[i-1]));
        if (it != diferencias.end()) diferencias.erase(it);
        
        max_acumulado = max(max_acumulado, max(0, b[i-1].valor - a[i-1]));
        res[b[i].idx] = max(max_acumulado, diferencias.empty() ? 0 : *diferencias.rbegin());
    }

    for (int i = 1; i <= n + 1; i++) cout << res[i] << (i == n + 1 ? "" : " ");
    return 0;
}

Problema 3: Suma XOR de Divisores

El reto consiste en calcular el XOR total de la suma XOR de todos los divisores para cada número entre $1$ y $n$. Una solución ingenua de $O(n)$ es insuficiente para valores grandes de $n$. Aplicamos una técnica de descomposición de raíz cuadrada (Square Root Decomposition) para optimizar el conteo de frecuencias de cada factor.

Sabemos que un factor $x$ contribuye al XOR total si aparece una cantidad impar de veces como divisor en el rango $[1, n]$. El número de múltiplos de $x$ en el rango es $\lfloor n/x \rfloor$.

#include <iostream>
#include <cmath>

using namespace std;

typedef long long ll;

ll obtener_xor_prefijo(ll v) {
    if (v <= 0) return 0;
    ll residuo = v % 4;
    if (residuo == 0) return v;
    if (residuo == 1) return 1;
    if (residuo == 2) return v + 1;
    return 0;
}

void procesar() {
    ll n;
    if (!(cin >> n)) return;
    ll acumulado = 0;
    ll limite = sqrt(n);
    ll d = 1;

    // Procesamiento hasta la raíz cuadrada
    for (; d <= limite; d++) {
        if ((n / d) % 2 != 0) {
            acumulado ^= d;
        }
    }

    // Procesamiento de bloques de cocientes constantes
    while (d <= n) {
        ll cociente = n / d;
        ll siguiente = n / cociente;
        if (cociente % 2 != 0) {
            acumulado ^= obtener_xor_prefijo(siguiente) ^ obtener_xor_prefijo(d - 1);
        }
        d = siguiente + 1;
    }
    cout << acumulado << endl;
}

int main() {
    procesar();
    return 0;
}

Etiquetas: dynamic programming Greedy Algorithms Square Root Decomposition Number Theory C++

Publicado el 6-20 23:34