Pierwiastkowa dekompozycja zapytań
W niniejszym artykule chciałbym przedstawić Czytelnikom technikę, która może okazać się pomocna przy rozwiązywaniu różnych problemów informatycznych.
Rozważmy przykładowe zadanie. Dana jest -elementowa tablica liczb naturalnych z przedziału oraz zapytań postaci gdzie Dla każdego zapytania należy odpowiedzieć na pytanie ile różnych elementów ma podtablica Można łatwo napisać algorytm, który dla zapytania znajduje odpowiedź w czasie a więc dla zapytań działa w czasie (kod źródłowy 1). Rozważmy jednak inne podejście do problemu. Zapamiętajmy wynik poprzedniego zapytania i spróbujmy zmodyfikować go tak, aby pasował do kolejnego (kod źródłowy 2).
Nasz algorytm wciąż rozwiązuje problem w czasie gdyż przejście z jednego zapytania do następnego może zająć czasu. Jednak gdybyśmy zagwarantowali, że kolejne zapytania będą podobne, algorytm mógłby działać szybciej. Rozważmy problem, w którym znamy na wstępie wszystkie zapytania. Możemy wybrać kolejność w jakiej będziemy na nie odpowiadać. Załóżmy dla uproszczenia, że jest kwadratem liczby naturalnej. Okazuje się, że istnieje kolejność, która gwarantuje, że nasz algorytm wykona jedynie operacji.
Podzielmy przedział na części:
Każdy z tych przedziałów jest rozmiaru Teraz utwórzmy kubełek dla każdego z nich. Dla każdego zapytania patrzymy do którego z tych kubełków wpada i umieszczamy to zapytanie w odpowiednim kubełku. Następnie w każdym kubełku sortujemy zapytania rosnąco po wartościach Przeglądamy teraz kubełki po kolei i odpowiadamy na zapytania zgodnie z ustalonym porządkiem.
Obliczmy złożoność naszego algorytmu. Rozważmy dwie sytuacje. W pierwszej z nich oba zapytania należą do tego samego kubełka. W takiej sytuacji stare_a oraz a należą do tego samego przedziału. Nie wykonamy zatem więcej niż operacji dodaj i usuń w trakcie zmiany stare_a na a. Jeśli chodzi o wartości stare_b oraz b to są one w ramach jednego kubełka posortowane rosnąco. Oznacza to, że jeśli dwa zapytania są w jednym kubełku, to będziemy wykonywać jedynie operację dodaj. A więc dla ustalonego kubełka sumarycznie wystarczy wykonać jedynie takich operacji przy zmianach stare_b na b. Rozważmy teraz sytuację w której zapytania należą do różnych kubełków. Jak powiedzieliśmy wcześniej, w najgorszym przypadku wykonamy operacji między dwoma zapytaniami. Jednak taka sytuacja zdarzy się co najwyżej razy, bo tylko tyle mamy kubełków. A więc sumarycznie nasz algorytm zadziała w czasie
A teraz dwa ćwiczenia dla Drogiego Czytelnika. Pierwsze jest następujące: mając daną tablicę oraz zapytań należy dla każdego zapytania odpowiedzieć ile inwersji znajduje się w podtablicy Inwersją w tablicy nazywamy taką parę liczb że oraz Drugie zaś brzmi: mając dany graf oraz tablicę jego krawędzi należy dla każdego zapytania odpowiedzieć na pytanie ile spójnych składowych ma graf obcięty do krawędzi Każde z tych zadań można rozwiązać za pomocą opisanej w artykule techniki, nazywanej pierwiastkową dekompozycją zapytań, jednakże wymagają one dodatkowego pomysłu. Mam nadzieję, że rozwiązywanie tych zadań przyniesie Czytelnikom dużo frajdy.