Tablas dispersas#691

Tablas dispersas (Editorial)

El problema requiere encontrar repetidamente el mínimo en un segmento de un arreglo que no cambia después de la generación. El número de consultas alcanza 10^7, por lo que cada consulta debe procesarse en . Para ello, usamos una sparse table — una estructura de datos estática para el problema RMQ (Range Minimum Query, consulta de mínimo en rango).

Construcción de la tabla. Para cada posición y cada tal que i + 2^j - 1 \le n, la celda mas[i][j] almacena el índice del elemento mínimo en el segmento [i; i + 2^j - 1] de longitud 2^j. Para j = 0 el segmento consiste de un solo elemento, por lo que mas[i][0] = i.

Para j > 0, dividimos el segmento de longitud 2^j en dos mitades de longitud 2^{j-1}: [i; i + 2^{j-1} - 1] y [i + 2^{j-1}; i + 2^j - 1]. Los índices de los mínimos en las mitades ya han sido calculados — son mas[i][j - 1] y mas[i + 2^{j-1}][j - 1]:

El mínimo de todo el segmento es igual al menor de los dos mínimos de las mitades. Por lo tanto, mas[i][j] es igual a cualquiera de los índices mas[i][j - 1] y mas[i + 2^{j-1}][j - 1] que apunte al elemento más pequeño del arreglo. La tabla se completa en orden creciente de y contiene O(n \log n) celdas.

Respuesta a una consulta. Supongamos que necesitamos encontrar el mínimo en el segmento . Sea k = \lfloor \log_2(v - u + 1) \rfloor el exponente de la mayor potencia de dos que no excede la longitud del segmento. El segmento de longitud 2^k que comienza en y el segmento de longitud 2^k que termina en juntos cubren completamente , ya que 2 \cdot 2^k > v - u + 1:

Estos segmentos pueden solaparse, pero esto no importa para el mínimo: un elemento contado dos veces no cambia el resultado (a diferencia de, por ejemplo, la suma). Por lo tanto, el mínimo en es igual al menor de los elementos a[mas[u][k]] y a[mas[v - 2^k + 1][k]], y una consulta se procesa en .

Los límites y de las consultas generadas pueden venir en cualquier orden, por lo que antes de ejecutar una consulta los intercambiamos si es necesario.

Complejidad. Construir la tabla toma O(n \log n), y cada consulta toma . La complejidad total es O(n \log n + m). Con m \le 10^7, es precisamente el tiempo de consulta lo que permite ajustarse al límite de tiempo.

Ejemplo

Consideremos el ejemplo: n = 10, m = 8, a_1 = 12345. El arreglo generado coincide con el arreglo dado en el enunciado del problema. La tabla para este arreglo se muestra a continuación. Las celdas contienen los índices de los elementos mínimos; un guion significa que el segmento se sale del arreglo.

\boldsymbol{j \setminus i}

—

—

—

—

—

—

—

—

—

—

—

Por ejemplo, mas[3][2] = 5: en el segmento , el elemento mínimo es a_5 = 1565158. El valor mas[2][3] = 2 se obtiene a partir de mas[2][2] = 2 y mas[6][2] = 8, ya que a_2 = 305498 < a_8 = 570265.

La primera consulta: u_1 = 3, v_1 = 9. La longitud del segmento es , por lo que k = 2. El segmento está cubierto por los segmentos y , para los cuales mas[3][2] = 5 y mas[6][2] = 8. Dado que a_8 = 570265 < a_5 = 1565158, la respuesta es ans_1 = 570265.

Los parámetros de la segunda consulta:

u_2 = ((17 \cdot 3 + 751 + 570265 + 2) \bmod 10) + 1 = 10, \\ v_2 = ((13 \cdot 9 + 593 + 570265 + 5) \bmod 10) + 1 = 1.

Dado que u_2 > v_2, se intercambian los límites, y se calcula el mínimo en el segmento .

Todas las consultas se listan en la tabla:

\boldsymbol{u_i}

\boldsymbol{v_i}

segmento

\boldsymbol{ans_i}

La última consulta tiene los parámetros u_8 = 5, v_8 = 3, y su respuesta es ans_8 = 1565158. Estos son exactamente los valores que se imprimen. Observe que se imprimen los valores originales u_8 y v_8, no los límites ordenados del segmento.

Implementación del algoritmo

Los elementos del arreglo (n \le 10^5) se almacenan comenzando desde el índice . La sparse table se almacena en el arreglo : mas[i][j] es el índice del elemento mínimo en el segmento [i; i + 2^j - 1]. Dado que 2^{16} \le 10^5 < 2^{17}, los valores de no superan , por lo que LOGMAX = 17 es suficiente para la segunda dimensión.

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

La función check intercambia los valores de y si u > v, de modo que u \le v. Los parámetros se pasan por referencia.

void check(int &u, int &v)
{
  int temp;
  if (u > v) temp = u, u = v, v = temp;
}

La función RMQ_nlogn construye la sparse table.

void RMQ_nlogn(void)
{
  int i, j;

Primero, mas[i][0] = i para todo .

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

Luego, para cada mientras 2^j \le n, y para cada tal que el segmento [i; i + 2^j - 1] no se salga del arreglo, elegir entre mas[i][j - 1] y mas[i + 2^{j-1}][j - 1] el índice del elemento más pequeño.

  for (j = 1; (1 << j) <= n; j++)
    for (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 q devuelve el índice del elemento mínimo en el segmento . Calcula k = \lfloor \log_2(j - i + 1) \rfloor y compara los mínimos de dos segmentos de longitud 2^k: [i; i + 2^k - 1] y [j - 2^k + 1; j].

int q(int i, int j)
{
  int k = (int)(log(1.0*j - i + 1) / log(2.0));
  int res = mas[i][k];
  if (a[mas[j - (1<<k) + 1][k]] < a[res])
      res = mas[j - (1<<k) + 1][k];
  return res;
}

La parte principal del programa. Leer , , el primer elemento del arreglo a_1, y los parámetros de la primera consulta y .

scanf("%d %d",&n,&m);
scanf("%d %d %d",&a[1],&u,&v);

Generar los elementos a_2, a_3, \ldots, a_n usando la fórmula del enunciado del problema. El producto 23 \cdot a_i no supera 23 \cdot 16714589 \approx 3.8 \cdot 10^8 y cabe en el tipo .

for(i = 2; i <= n; i++)
  a[i] = (23 * a[i-1] + 21563) % 16714589;

Construir la sparse table .

RMQ_nlogn();

Procesar las consultas una por una.

for (i = 1; i <= m; i++)
{

Copiar los límites de la consulta actual en y y, si es necesario, ordenarlos con la función check. Los valores y en sí no se modifican: los parámetros de la siguiente consulta se calculan a partir de ellos, y son los que se imprimen en la respuesta.

  u1 = u; v1 = v; check(u1,v1);

Encontrar el elemento mínimo en el segmento [u1; v1].

  ans = a[q(u1,v1)];

Si la consulta no es la última, calcular los parámetros de la siguiente consulta usando las fórmulas del enunciado del problema.

  if (i < m)
  {
    u = ((17*u + 751 + ans + 2*i) % n) + 1;
    v = ((13*v + 593 + ans + 5*i) % n) + 1;
  }
}

Imprimir los parámetros de la última consulta y su respuesta.

printf("%d %d %d\n",u,v,ans);