Przeskocz do treści

Delta mi!

Informatyczny kącik olimpijski

Zliczanie prostokątów

Adam Karczmarz

o artykule ...

  • Publikacja w Delcie: styczeń 2012
  • Publikacja elektroniczna: 01-01-2012
  • Autor: Adam Karczmarz
    Afiliacja: student, Wydział Matematyki, Informatyki i Mechaniki Uniwersytetu Warszawskiego

W tym kąciku rozwiążemy zadanie Prostokąty (Rectangles), które pojawiło się w 2007 roku na konkursie organizowanym przez MIT w serwisie spoj.pl.

Zadanie. Na płaszczyźnie danych jest math różnych punktów. Na ile sposobów można wybrać cztery z nich tak, aby były one wierzchołkami prostokąta o bokach równoległych do osi układu współrzędnych?

Pierwsze rozwiązanie, które powinno przyjść nam do głowy, wykorzystuje obserwację, że każdy szukany prostokąt jest jednoznacznie określony przez jego dwa przeciwległe wierzchołki. Możemy więc dla każdej pary danych punktów (lewy dolny, prawy górny) sprawdzić, czy do zbioru należą dwa pozostałe wierzchołki. Jeśli do sprawdzania przynależności do zbioru wykorzystamy tablicę haszującą, takie rozwiązanie będzie działało w czasie math

Może się wydawać, że sporo czasu w powyższym rozwiązaniu tracimy na sprawdzaniu „złych” par – przekątnych, dla których pozostałe dwa punkty nie istnieją. Niestety, prosty przykład math punktów ustawionych w „dwuszereg” pokazuje, że wynik może być rzędu math  zatem żaden algorytm zliczający prostokąty pojedynczo nie może być istotnie lepszy od powyższego.

Jak zatem znaleźć lepsze rozwiązanie? Zauważmy na początek, że możemy przenumerować współrzędne na wejściu tak, aby wszystkie były naturalne i nie większe niż math Nie interesują nas bowiem długości boków czy pola prostokątów, tylko ich liczba. Podzielmy punkty na pionowe bloki, tj. ze względu na współrzędną math Bloki natomiast podzielmy na dwie kategorie – małe, czyli te, które zawierają co najwyżej math punktów, i duże, czyli pozostałe.

Zapomnijmy na moment o dużych blokach. Powiemy, że współrzędna math należy do bloku, jeśli w bloku znajduje się punkt o drugiej współrzędnej równej math Będziemy iterować po możliwych położeniach dolnego boku prostokąta, od góry do dołu. Załóżmy, że dolny bok prostokąta ma drugą współrzędną równą math i rozważmy te bloki, do których należy punkt o takiej współrzędnej – punkty w pozostałych blokach nie mają szans należeć do takiego prostokąta. Ignorujemy także wszystkie punkty o drugich współrzędnych nie większych niż math – te także nie mają wpływu na wynik. Teraz wystarczy policzyć, ile jest poziomych odcinków o obu końcach wśród punktów, które nam pozostały – jako że każdy blok zawiera math każdy taki odcinek odpowiada jednemu prostokątowi. Ten krok można wykonać łatwo, przechodząc po wszystkich punktach, które nam pozostały (w dowolnej kolejności, np. od lewej do prawej), przy okazji zliczając w tablicy (współrzędne są małe!), ile razy dana rzędna już wystąpiła, i aktualizując wynik.

Załóżmy, że wielkości małych bloków to math przy czym math i  math Przy uważnej implementacji algorytmu małych bloków wykonamy w nim liczbę operacji co najwyżej rzędu math Faktycznie, wybrana współrzędna math będzie należała do math-tego bloku math razy, i wówczas w fazie przeglądania każdy punkt tego bloku odwiedzimy co najwyżej raz. Wykażemy, że math  Spróbujmy znaleźć „najgorszy możliwy” ciąg math Jeśli dla pewnych różnych math mamy math to zmniejszając math o math i zwiększając math o math nie zmieniamy math a jednocześnie zwiększamy math Jest to konsekwencja wypukłości funkcji kwadratowej. Powtarzając tę operację dostatecznie dużo razy, otrzymamy wszystkie liczby math – być może z wyjątkiem jednej – równe math lub math Tych niezerowych będzie co najwyżej math ponieważ math Ostatecznie, dostajemy

display-math

Pozostało nam zająć się dużymi blokami. Dla dowolnego bloku math łatwo policzyć w czasie math  ile jest prostokątów, które mają dwa punkty w tym bloku – jeśli dla każdego pozostałego bloku math znamy liczbę współrzędnych math wspólnych dla math i math to szukaną przez nas liczbą jest math Ponieważ tę sumę dodamy do wyniku dla każdego dużego bloku, więc prostokąty o wszystkich punktach w dużych blokach policzymy dwukrotnie math To jednak nie jest duży problem – aby się z nim uporać, wystarczy dla każdego dużego bloku zawrzeć w sumie wszystkie małe bloki i tylko te duże, które znajdują się na lewo od niego. Na szczęście, duże bloki są wystarczająco liczne, aby nie mogło ich być więcej niż math – a więc ta faza działa także w czasie math W powyższych rozważaniach zapomnieliśmy o kosztach związanych z sortowaniem i przenumerowaniem wejściowych współrzędnych. Te nie powinny wynieść jednak więcej niż math operacji, więc są znikome w porównaniu z kosztem faz zliczających. Całe rozwiązanie działa więc w czasie math  i pamięci math

Zainteresowanemu Czytelnikowi polecamy zastanowienie się, co by było, gdybyśmy chcieli, zamiast prostokątów, zliczać kwadraty. Czy umiemy to zrobić szybciej, czy potrzebujemy bardziej skomplikowanych struktur danych? Tak sformułowane zadanie pojawiło się na Obozie Naukowo-Treningowym im. A. Kreczmara w 2010 roku pod tytułem Ogródek i można je rozwiązywać w serwisie main.edu.pl.