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;
}