Máximo#4473

Máximo (Editorial)

El problema requiere responder consultas de máximo en rango. Los elementos del arreglo no cambian, por lo que una estructura de datos estática es adecuada. El número de consultas alcanza 5 \cdot 10^5, y la longitud del arreglo es 10^5. Calcular el máximo de cada segmento directamente requeriría hasta 5 \cdot 10^{10} operaciones, lo cual es demasiado lento.

Utilizamos una sparse table para el problema RMQ, pero en lugar del mínimo buscamos el máximo (Range Maximum Query). El máximo, al igual que el mínimo, no cambia si algún elemento se cuenta dos veces. Por lo tanto, el segmento de consulta puede cubrirse con dos segmentos superpuestos de longitud 2^k, y se elige el mayor de sus máximos.

Una celda de la tabla puede almacenar ya sea el índice del elemento máximo o el máximo en sí. El problema requiere imprimir solo el valor, por lo que basta con almacenar el máximo. La variante con índice es necesaria cuando se debe conocer la posición del máximo. A continuación se presentan ambas implementaciones.

Según el enunciado del problema, 1 \le l, r \le n, y no se garantiza que l \le r. Por lo tanto, antes de ejecutar una consulta, intercambiamos los límites si es necesario.

Complejidad. Construir la sparse table toma O(n \log n), y cada consulta toma . La complejidad total es O(n \log n + q).

Ejemplo

Consideremos el arreglo del ejemplo: (5, 1, 2, 8, 7, 6, 10, 7, 5, 6). La tabla , en la cual mas[i][j] es el máximo en el segmento [i; i + 2^j - 1], se muestra a continuación. Un guion significa que el segmento se sale del arreglo.

\boldsymbol{j \setminus i}

—

—

—

—

—

—

—

—

—

—

—

Por ejemplo, mas[4][2] = \max(mas[4][1], mas[6][1]) = \max(8, 10) = 10 — este es el máximo en el segmento .

Consideremos la consulta . La longitud del segmento es , y la mayor potencia de dos que no supera es , por lo que k = 2. El segmento se cubre con los segmentos y , que se superponen en el segmento :

La respuesta es \max(mas[2][2], mas[3][2]) = \max(8, 8) = 8. El elemento pertenece a ambos segmentos, pero esto no afecta el valor del máximo.

Todas las consultas del ejemplo se enumeran en la tabla:

consulta

segmentos

máximos

respuesta

,

,

,

,

,

,

,

,

Para segmentos de longitud y (consultas , y ), ambos segmentos de cobertura coinciden con el propio segmento de la consulta.

Implementación del algoritmo — índice del máximo

La secuencia de entrada a_i (n \le 10^5) se almacena en el arreglo comenzando desde el índice . En esta variante, la celda mas[i][j] almacena el índice del elemento máximo en el segmento [i; i + 2^j - 1], y el máximo en sí es igual a a[mas[i][j]]. Dado que 2^{16} \le 10^5 < 2^{17}, LOGMAX = 17 es suficiente para la segunda dimensión.

#define MAX 100010
#define LOGMAX 17
int mas[MAX][LOGMAX];
int a[MAX];

La función Build_RMQ_Array construye la sparse table de índices.

void Build_RMQ_Array()
{

Para j = 0, el segmento consiste de un solo elemento, por lo que mas[i][0] = i.

  for (int i = 1; i <= n; i++) mas[i][0] = i;

Para j > 0, se comparan los elementos del arreglo con índices mas[i][j - 1] y mas[i + 2^{j-1}][j - 1] — los máximos en las dos mitades del segmento — y se almacena el índice del mayor.

  for (int j = 1; (1 << j) <= n; j++)
    for (int i = 1; i + (1 << j) - 1 <= n; i++)
      if (a[mas[i][j - 1]] > a[mas[i + (1 << (j - 1))][j - 1]])
        mas[i][j] = mas[i][j - 1];
      else mas[i][j] = mas[i + (1 << (j - 1))][j - 1];
}

La función RangeMaxQuery devuelve el índice del elemento máximo en el segmento .

int RangeMaxQuery(int i, int j)
{
  int temp, k = 0;

Si i > j, se intercambian los límites.

  if (i > j) temp = i, i = j, j = temp;

Luego, en un ciclo, se encuentra el mayor tal que 2^k \le j - i + 1.

  while ((1 << (k + 1)) <= j - i + 1) k++;

Se comparan los elementos con índices mas[i][k] y mas[j - 2^k + 1][k] y se devuelve el índice del mayor.

  return (a[mas[i][k]] >= a[mas[j - (1 << k) + 1][k]]) ?
         mas[i][k] : mas[j - (1 << k) + 1][k];
}

Parte principal del programa. Se lee el tamaño del arreglo y sus elementos, comenzando desde el índice .

scanf("%d", &n);
for (i = 1; i <= n; i++)
  scanf("%d", &a[i]);

Se construye la sparse table a partir del arreglo .

Build_RMQ_Array();

Se lee el número de consultas .

scanf("%d", &q);

Para cada consulta, la función RangeMaxQuery devuelve el índice del elemento máximo en el segmento , y se imprime el elemento en sí.

for (i = 0; i < q; i++)
{
  scanf("%d %d", &u, &v);
  int index = RangeMaxQuery(u, v);
  printf("%d\n", a[index]);
}

Implementación del algoritmo — máximo

La secuencia de entrada a_i (n \le 10^5) se almacena en el arreglo comenzando desde el índice . En esta variante, la celda mas[i][j] almacena el máximo en sí en el segmento [i; i + 2^j - 1]. Dado que 2^{16} \le 10^5 < 2^{17}, LOGMAX = 17 es suficiente para la segunda dimensión.

#define MAX 100010
#define LOGMAX 17
int mas[MAX][LOGMAX];
int a[MAX];

La función Build_RMQ_Array construye la sparse table a partir del arreglo :

mas[i][j] = \max(b[i], b[i + 1], \ldots, b[i + 2^j - 1])
void Build_RMQ_Array(int *b)
{

Para j = 0, la sparse table contiene los elementos originales del arreglo: mas[i][0] = b[i].

  for (int i = 1; i <= n; i++) mas[i][0] = b[i];

Para j > 0, el valor mas[i][j] es igual al mayor de los máximos en las dos mitades del segmento.

  for (int j = 1; 1 << j <= n; j++)
    for (int i = 1; i + (1 << j) - 1 <= n; i++)
      mas[i][j] = max(mas[i][j - 1], mas[i + (1 << (j - 1))][j - 1]);
}

La función RangeMaxQuery devuelve el máximo en el segmento .

int RangeMaxQuery(int i, int j)
{
  int temp, k = 0;

Si i > j, se intercambian los límites.

  if (i > j) temp = i, i = j, j = temp;

En un ciclo, se encuentra el mayor tal que 2^k \le j - i + 1. Esta forma de calcular no requiere la función logaritmo y no depende de errores de punto flotante.

  while ((1 << (k + 1)) <= j - i + 1) k++;

La respuesta es el mayor de los máximos en los segmentos [i; i + 2^k - 1] y [j - 2^k + 1; j].

  return max(mas[i][k],mas[j - (1<<k) + 1][k]);
}

Parte principal del programa. Se lee el tamaño del arreglo y sus elementos, comenzando desde el índice .

scanf("%d",&n);
for(i = 1; i <= n; i++) scanf("%d",&a[i]);

Se construye la sparse table a partir del arreglo .

Build_RMQ_Array(a);

Se lee el número de consultas . Para cada consulta, se imprime el máximo en el segmento .

scanf("%d",&q);
for(i = 0; i < q; i++)
{
  scanf("%d %d",&u,&v);
  printf("%d\n",RangeMaxQuery(u,v));
}