Sparse table
Uvod
Sparse table je struktura podataka koja nad statičkim nizovima može obaviti min/max upite nad intervalima u . Kako bismo to ostvarili, potrebno je nad nizom obaviti pretprocesiranje i izgraditi sparse table. Složenost pretprocesiranja je . Ovdje ćemo prikazati funkcionalnosti koristeći sparse table za min upite, a analogno se gradi i za max upite. Cilj nam je imati zapisano u matrici rješenja min upita za sve duljine koje su potencije broja 2. Dakle, imamo matricu , gdje predstavlja indeks početka intervala, a predstavlja eksponent pri potenciranju broja 2 što označava duljinu intervala.
Primjer:
- minimalni broj na intervalu koji počinje na indexu 3, a duljine je
- početak intervala je 2, a on je duljine
- početak intervala je 1, a on je duljine
Ako ovo prikažemo formulom:
Pretprocesiranje
Prvi korak je upisati rješenja za sve duljine 1 (kad je ). U tom slučaju samo prepišemo vrijednosti iz početnog niza:
Imamo rješenja za intervale:
Sad računamo za intervale duljine 2 .
Primijetimo da se svaki taj interval može izračunati pomoću intervala duljine 1 koje već imamo zapisane.
Minimalni broj na intervalu je manji od i .
Nadalje, , i tako za svaki element niza (osim zadnjeg, jer iz njega ne može početi interval duljine 2).
Kako to prikazati pomoću naše matrice?
Nastavljamo s intervalima duljine 4 . Primijetimo da se interval može prikazati kao unija intervala i , a podatak o najmanjem broju za ta dva intervala smo već izračunali.
Vrijedi:
Postupak se nastavlja za sve dok je duljina intervala manja ili jednaka duljini niza.
Primjer pretprocesiranja
Pogledajmo sljedeći primjer:
Imamo niz duljine , i iz njega je potrebno napraviti sparse table. Ispod se nalazi slika kako bi lakše vizualizirali postupak.
Prvo ispunjavamo sparseTable za - žuti redak sa slike. Kao što je prije rečeno, samo prepišemo vrijednosti iz niza.
Nakon toga, za - plavi dio sa slike, računamo gdje vrijedi koristeći podatke iz prošlog koraka. Npr. za (interval ) uzet ćemo (interval ) i (interval ), a manji od ta dva broja zapisujemo kao rezultat.
Nastavljamo s . Opet računamo rješenje koristeći rezultate iz prošlog koraka. Na sljedećim slikama je prikazano koja polja se koriste prilikom računanja rezultata.
Postupak se nastavlja dok je , tj. dok interval ne izlazi iz granica niza.
Ovime smo završili pretprocesiranje i izgradili sparse table.
Formula:
Zašto baš ? Jer nam treba desna polovica intervala duljine , dakle početnom indeksu pridodajemo pola duljine niza.
Programski kod
vector < vector<int> > createSparseTable(vector <int> arr) {
int N = arr.size();
int K = log2(N); //2^K je najveca duljina intervala koja ne prelazi duljinu niza
vector <int> row(K + 1);
vector < vector<int> > sparseTable(N, row);
for(int i = 0; i < N; i++) { // j = 0
sparseTable[i][0] = arr[i]; //intervali duljine 2^0 = 1
}
for(int j = 1; j <= K; ++j) {
int len = pow(2, j); //duljina intervala
for(int i = 0; i + len - 1 < N; ++i) { //uvjet je da interval [i, i + 2^j - 1] ne prelazi izvan granica niza
sparseTable[i][j] = min(sparseTable[i][j - 1], sparseTable[i + len/2][j - 1]);
}
}
return sparseTable;
}
Često se umjesto koristi logički posmak ulijevo (engl. bitshift) za potencije broja 2.
Upiti
Kako sada odgovoriti na upit za najmanji broj nad nekim intervalom? Uzmemo najveću potenciju broja 2, a da ne prelazi duljinu intervala. Na primjer, tražimo rješenje za interval . Najveća potencija broja 2 u ovom slučaju je 4.
Vrijedi i sad radimo upit za (početak intervala je 2, a duljina ). Osim toga uzimamo upit i za desni dio intervala, točnije , a to je . Uzmemo manju vrijednost i ona je rješenje.
Možemo vidjeti da ta dva upita uistinu pokrivaju cijeli interval.
Konačno, formula za dohvat najmanjeg broja na intervalu (duljina intervala = R - L + 1):