Resolución del problema Lightning Conductor usando tablas dispersas y optimización de decisiones

El problema Lightning Conducter (POI2011) requiere calcular para cada posición i el valor máximo de la expresión:

[\max\left{a_j+\left\lceil\sqrt{|i-j|}\right\rciel\right}-a_i ]

Una solución directa con complejidad (O(n\sqrt{n})) utiliza una tabla de máximos (ST table) para rangos. Dado que (\sqrt{|i-j|}) tiene aproximadaemnte (\sqrt{n}) valores distintos, podemos iterar sobre estos valores y consultar rápidamente el máximo en los intervalos correspondientes.

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;

const int MAX_N = 500005;
int log_table[MAX_N], st_max[MAX_N][20], valores[MAX_N], n;

void inicializar_st() {
    for (int i = 2; i <= n; i++) 
        log_table[i] = log_table[i >> 1] + 1;
    for (int i = 1; i <= n; i++) 
        st_max[i][0] = valores[i];
    for (int j = 1; j < 20; j++)
        for (int i = 1; i + (1 << j) - 1 <= n; i++)
            st_max[i][j] = max(st_max[i][j-1], st_max[i + (1<<(j-1))][j-1]);
}

int consultar_maximo(int l, int r) {
    int k = log_table[r - l + 1];
    return max(st_max[l][k], st_max[r - (1<<k) + 1][k]);
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> valores[i];
    inicializar_st();
    
    for (int i = 1; i <= n; i++) {
        int resultado = 0;
        for (int k = 1; ; k++) {
            int inicio = (k-1)*(k-1) + 1, fin = k*k;
            if (i + inicio <= n) {
                int seg_max = consultar_maximo(i+inicio, min(n, i+fin));
                resultado = max(resultado, seg_max + k - valores[i]);
            }
            if (i - inicio >= 1) {
                int seg_max = consultar_maximo(max(1, i-fin), i-inicio);
                resultado = max(resultado, seg_max + k - valores[i]);
            }
            if (i - fin <= 1 && i + fin >= n) break;
        }
        cout << resultado << endl;
    }
    return 0;
}

Una solución más eficiente ((O(n \log n))) aprovecha la monotonicidad de las decisiones mediante divide y vencerás. Para cada subrango, se encuentra el punto óptimo para el centro y luego se recursa en los subrangos izquierdo y derecho.

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;

const int MAX_N = 500005;
double raices[MAX_N], resultados[MAX_N];
int n, arr[MAX_N];

void resolver(int left, int right, int opt_l, int opt_r) {
    if (left > right) return;
    int mid = (left + right) / 2;
    int mejor_pos = mid;
    double mejor_val = arr[mid];
    for (int i = opt_l; i <= min(opt_r, mid); i++) {
        double candidato = arr[i] + raices[mid - i];
        if (candidato > mejor_val) {
            mejor_val = candidato;
            mejor_pos = i;
        }
    }
    resultados[mid] = max(resultados[mid], mejor_val - arr[mid]);
    resolver(left, mid-1, opt_l, mejor_pos);
    resolver(mid+1, right, mejor_pos, opt_r);
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> arr[i];
        raices[i] = sqrt(i);
    }
    resolver(1, n, 1, n);
    reverse(arr+1, arr+n+1);
    reverse(resultados+1, resultados+n+1);
    resolver(1, n, 1, n);
    for (int i = n; i >= 1; i--)
        cout << ceil(resultados[i]) << endl;
    return 0;
}

Etiquetas: ST-table divide-y-venceras optimización POI algoritmos

Publicado el 9-16 07:21