Tabelle sparse#691

Tabelle sparse (Editoriale)

Il problema richiede di trovare ripetutamente il minimo su un segmento di un array che non cambia dopo la generazione. Il numero di query arriva fino a 10^7, quindi ogni query deve essere elaborata in . Per fare ciò, utilizziamo una sparse table — una struttura dati statica per il problema RMQ (Range Minimum Query).

Costruzione della tabella. Per ogni posizione e ogni tale che i + 2^j - 1 \le n, la cella mas[i][j] memorizza l'indice dell'elemento minimo sul segmento [i; i + 2^j - 1] di lunghezza 2^j. Per j = 0 il segmento è costituito da un solo elemento, quindi mas[i][0] = i.

Per j > 0, dividiamo il segmento di lunghezza 2^j in due metà di lunghezza 2^{j-1}: [i; i + 2^{j-1} - 1] e [i + 2^{j-1}; i + 2^j - 1]. Gli indici dei minimi sulle due metà sono già stati calcolati — sono mas[i][j - 1] e mas[i + 2^{j-1}][j - 1]:

Il minimo dell'intero segmento è uguale al più piccolo dei due minimi delle metà. Pertanto, mas[i][j] è uguale a quale dei due indici mas[i][j - 1] e mas[i + 2^{j-1}][j - 1] punta all'elemento più piccolo dell'array. La tabella viene riempita in ordine crescente di e contiene O(n \log n) celle.

Risposta a una query. Supponiamo di dover trovare il minimo sul segmento . Sia k = \lfloor \log_2(v - u + 1) \rfloor l'esponente della più grande potenza di due che non supera la lunghezza del segmento. Il segmento di lunghezza 2^k che inizia in e il segmento di lunghezza 2^k che termina in coprono insieme completamente , poiché 2 \cdot 2^k > v - u + 1:

Questi segmenti possono sovrapporsi, ma ciò non ha importanza per il minimo: un elemento contato due volte non cambia il risultato (a differenza, ad esempio, della somma). Pertanto, il minimo su è uguale al più piccolo tra gli elementi a[mas[u][k]] e a[mas[v - 2^k + 1][k]], e una query viene elaborata in .

Gli estremi e delle query generate possono comparire in qualsiasi ordine, quindi prima di eseguire una query li scambiamo se necessario.

Complessità. La costruzione della tabella richiede O(n \log n), e ogni query richiede . La complessità complessiva è O(n \log n + m). Con m \le 10^7, è proprio il tempo di query a rendere possibile rientrare nel limite di tempo.

Esempio

Consideriamo l'esempio: n = 10, m = 8, a_1 = 12345. L'array generato coincide con l'array fornito nell'enunciato del problema. La tabella per esso è mostrata di seguito. Le celle contengono gli indici degli elementi minimi; un trattino indica che il segmento va oltre i limiti dell'array.

\boldsymbol{j \setminus i}

—

—

—

—

—

—

—

—

—

—

—

Ad esempio, mas[3][2] = 5: sul segmento , l'elemento minimo è a_5 = 1565158. Il valore mas[2][3] = 2 si ottiene da mas[2][2] = 2 e mas[6][2] = 8, poiché a_2 = 305498 < a_8 = 570265.

La prima query: u_1 = 3, v_1 = 9. La lunghezza del segmento è , quindi k = 2. Il segmento è coperto dai segmenti e , per i quali mas[3][2] = 5 e mas[6][2] = 8. Poiché a_8 = 570265 < a_5 = 1565158, la risposta è ans_1 = 570265.

I parametri della seconda query:

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

Poiché u_2 > v_2, gli estremi vengono scambiati, e viene calcolato il minimo sul segmento .

Tutte le query sono elencate nella tabella:

\boldsymbol{u_i}

\boldsymbol{v_i}

segmento

\boldsymbol{ans_i}

L'ultima query ha i parametri u_8 = 5, v_8 = 3, e la sua risposta è ans_8 = 1565158. Questi sono esattamente i valori che vengono stampati. Si noti che vengono stampati i valori originali u_8 e v_8, non gli estremi ordinati del segmento.

Implementazione dell'algoritmo

Gli elementi dell'array (n \le 10^5) sono memorizzati a partire dall'indice . La sparse table è memorizzata nell'array : mas[i][j] è l'indice dell'elemento minimo sul segmento [i; i + 2^j - 1]. Poiché 2^{16} \le 10^5 < 2^{17}, i valori di non superano , quindi LOGMAX = 17 è sufficiente per la seconda dimensione.

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

La funzione check scambia i valori di e se u > v, in modo che u \le v. I parametri vengono passati per riferimento.

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

La funzione RMQ_nlogn costruisce la sparse table.

void RMQ_nlogn(void)
{
  int i, j;

Prima di tutto, mas[i][0] = i per tutti gli .

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

Poi, per ogni finché 2^j \le n, e per ogni tale che il segmento [i; i + 2^j - 1] non vada oltre i limiti dell'array, si sceglie tra mas[i][j - 1] e mas[i + 2^{j-1}][j - 1] l'indice dell'elemento più piccolo.

  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 funzione q restituisce l'indice dell'elemento minimo sul segmento . Si calcola k = \lfloor \log_2(j - i + 1) \rfloor e si confrontano i minimi di due segmenti di lunghezza 2^k: [i; i + 2^k - 1] e [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 principale del programma. Si leggono , , il primo elemento dell'array a_1 e i parametri della prima query e .

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

Si generano gli elementi a_2, a_3, \ldots, a_n utilizzando la formula dell'enunciato. Il prodotto 23 \cdot a_i non supera 23 \cdot 16714589 \approx 3.8 \cdot 10^8 e rientra nel tipo .

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

Si costruisce la sparse table .

RMQ_nlogn();

Si elaborano le query una per una.

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

Si copiano gli estremi della query corrente in e e, se necessario, li si ordina con la funzione check. I valori e stessi non vengono modificati: da essi vengono calcolati i parametri della query successiva, e sono quelli che vengono stampati nella risposta.

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

Si trova l'elemento minimo sul segmento [u1; v1].

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

Se la query non è l'ultima, si calcolano i parametri della query successiva utilizzando le formule dell'enunciato.

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

Si stampano i parametri dell'ultima query e la sua risposta.

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