http://pl.spoj.com/problems/BAJTIOS3
Skrócony opis problemu:
Otrzymujemy n \le 100000 liczb. Każda z nich (x_i) ma przypisany indeks i. Następnie otrzymujemy m \le 10000 trójek liczb: a, b, y. Zadanie polega na znalezieniu dla każdego zapytania (j-tego) ilości takich x_i, że i \in \left<a;b\right> oraz x_i > y_j.
Podejście naiwne:
Dla każdego zapytania sprawdzamy po prostu wszystkie n liczb i zliczamy te spełniające wymagania.
Złożoność: O(m \cdot n).
Podejście sprytne:
Możemy to zrobić drzewem przedziałowym drzew. Drzewo przedziałowe odpowiedzialne będzie za indeksy liczb, a drzewo dla każdej komórki w drzewie przedziałowym będzie odpowiedzialne za wartości liczb. Czyli będziemy dodawać najpierw te n liczb do drzewa przedziałowego drzew w następujący sposób. Najpierw musimy wybrać indeksy drzew, które mamy zamiar uzupełnić. Robimy to w czasie logarytmicznym. Dla każdej komórki, która odpowiada za przedział w drzewie przedziałowym mamy jakieś drzewo zrównoważone. Do każdego drzewa w komórce którą odwiedzamy dodajemy (również w czasie logarytmicznym) wartość liczby.
Złożoność tej części wynosi O\left(n \cdot \log^2{n}\right), bo dla każdej z n liczb przechodzimy drzewo przedziałowe w czasie logarytmicznym, a może się pesymistycznie zdarzyć, że to będzie cały czas ten sam przedział, więc liczb w niektórych komórkach będzie n, więc dodawanie też zajmie logarytmiczny czas.
Następnie musimy jakoś obsłużyć zapytania. No więc dla przedziału z każdego zapytania \left<a;b\right> sprawdzamy w czasie logarytmicznym komórki, które nas interesują, a w każdej z nich sprawdzamy w drzewie zrównoważonym ile jest liczb większych od y_j z danego zapytania (również w czasie logarytmicznym).
Złożoność tej części to O\left(m \cdot \log^2{n}\right).
Widzimy więc, że pierwszy poziom (drzewo przedziałowe) odpowiedzialny jest za warunek indeksów: i \in \left<a;b\right>, a drugi (drzewo zrównoważone) za warunek wartości: x_i > y_j. Końcowa złożoność to zatem O\left((m+n) \cdot \log^2{n}\right).
Podejście sprytniejsze:
Możemy coś zauważyć i z poprzedniego drzewa drzew wyeliminować jeden poziom. Musielibyśmy zatem zamienić dwa warunki na jeden. Tym poziomem będzie drzewo wartości. Jeśli bowiem będziemy mieć w drzewie przedziałowym tylko wartości większe od danego y_j, to będziemy je wszystkie brali pod uwagę i można się będzie obyć bez sprawdzania drugiego warunku. No ale jak to zrobić? Możemy sobie posortować nasze n liczb oraz osobno nasze zapytania - oba niemalejąco i z zapamiętaniem pierwotnej kolejności, w której zostały pobrane. No więc mamy sobie wszystko posortowane. I co nam to daje? No więc na początku tablicy mamy największe wartości. Możemy więc porównać wartości początkowe z tablic. Jeśli ta z tablicy y będzie większa (lub równa), to znaczy, że w tablicy x nie ma żadnej liczby spełniającej założenia, więc wpisujemy 0 do tablicy wynikowej o indeksie zapamiętanym dla danego zapytania (czyli tym pierwotnym miejscu, w którym się znajdował w zapytaniach, żeby również wypisać w odpowiedniej kolejności wyniki) i idziemy dalej w tablicy y (w tablicy x znajdujemy się nadal w tym samym miejscu). Teraz mamy już inne, mniejsze (lub równe) y_j. Okazuje się, że ono jest tym razem mniejsze od największego elementu w tablicy x. Dodajemy więc to x_i do naszego drzewa przedziałowego, jako że będzie już rozpatrywane przez wszystkie pozostałe zapytania. Bo skoro zapytania są w porządku niemalejącym i dla danego zapytania x_i > y_j, to dla następnych tym bardziej skoro kolejne y_{j+k} są mniejsze od y_j. A jak dodajemy to x_i? Tym razem nie zależy nam na wartości tego x_i, bo już nie będziemy musieli porównywać go z y_j. Wystarczy nam wiedzieć, że jest takie x_i i w jakim miejscu się ono znajduje. Dodajemy więc 1 do komórek przedziałów w drzewie, w których to przedziałach zawiera się pierwotny indeks x_i (który powinniśmy byli zapamiętać). No ale może się zdarzyć, że również x_{i+1}>y_j. Przechodzimy więc tablicę x i dodajemy x_i do drzewa przedziałowego aż nie napotkamy takiego x_i, który będzie mniejszy lub równy y_j. Jak już takie x_i znajdziemy (albo skończą nam się x_i) to możemy przeszukać nasze drzewo przedziałowe zliczając ilość liczb, które już spełniają warunek x_i>y_j i skupiając się tylko na właściwym przedziale (wykonując to w czasie logarytmicznym). Na końcu wypisujemy w odpowiedniej kolejności stablicowane wcześniej wyniki. Bo pomieszaliśmy zapytania, więc musimy zapamiętać ich kolejność i mając dla każdego wyniki odtworzyć poprawny ciąg odpowiedzi.
Złożoność końcowa to O\left((n+m) \cdot \log{(n+m)}\right), bo najpierw sortujemy obie tablice (O(n \cdot \log{n} + m \cdot \log{m})), a potem przechodzimy tablice liniowo i dla każdej komórki x i y wykonujemy O(\log{n}) operacji.
Ostateczny algorytm (oparty na drzewie Fenwicka; można zrobić na zwykłym drzewie przedziałowym przy takiej samej złożoności asymptotycznej):
struct para { int i; float x; bool operator<(const para &a)const { return x > a.x; } }tab2[100009]; // tablica przechowująca x_i struct el:para{int a,b;}q[10009]; // tablica przechowująca zapytania - y_i int tab[1<<17], ans[10009]; // tablica na drzewo Fenwicka oraz na wynik int sum(int a) // zliczenie sumy komórek od 0 do a { int s = 0; do s += tab[a]; while(a -= a & -a); return s; } int sum(int a, int b) // zliczenie sumy komórek od a do b { return sum(b) - (a ? sum(a-1) : 0); } void add(int a, int x, int n) // dodanie x do komórki a { do tab[a] += x; while((a += a & -a) < n); } int main() { int n, n2, m, i, j; scanf("%d", &n); n2 = n; if(n2-1 & n2) // rozszerzanie n do potęgi 2 jeśli nią nie jest { frexp(n2, &n2); n2 = 1 << n2; } for(i = 1; i <= n; ++i) scanf("%f", &tab2[i].x), tab2[i].i = i; scanf("%d", &m); for(i = 0; i < m; ++i) scanf("%d %d %f", &q[i].a, &q[i].b, &q[i].x), q[i].i = i; sort(tab2+1, tab2+n+1); sort(q, q+m); for(i = 0, j = 1; i < m; ++i) // dla każdego zapytania { while(j <= n && tab2[j].x > q[i].x) // dopóki istnieje x_j większe od y_i add(tab2[j--].i, 1, n2); // dodaj to x_j do drzewa Fenwicka ans[q[i].i] = sum(q[i].a, q[i].b); // sprawdź ile jest x w danym przedziale i zapisz to do tymczasowej tablicy } for(i = 0; i < m; ++i) // wypisywanie wyników w odpowiedniej kolejności printf("%d\n", ans[i]); }
Brak komentarzy:
Prześlij komentarz