Informatyczny kącik olimpijski
Zliczamy puste prostokąty
W tym miesiącu zajmiemy się dość klasycznym zadaniem. Dany jest kwadrat rozmiaru podzielony na pól, przy czym niektóre pola są zabronione. Dowolny zawarty w tym kwadracie prostokąt, który nie zawiera żadnego pola zabronionego, nazwiemy prostokątem pustym. Należy znaleźć pusty prostokąt o jak największym polu.
To zadanie nie powinno być nowością ani dla miłośników zadań olimpijskich (pojawiło się m.in. na IX Olimpiadzie Informatycznej jako zadanie pt. Działka), ani dla stałych czytelników Delty (omawialiśmy jego rozwiązanie w artykule Prostokąt arytmetyczny w numerze 3/2012). Co więcej, różne modyfikacje tego zadania pojawiały się też (i nadal pojawiają) na innych konkursach. Przykładowe modyfikacje mogą polegać na tym, że jesteśmy proszeni o znalezienie pustego prostokąta o największym obwodzie, liczby pustych prostokątów czy też sumarycznego pola powierzchni wszystkich pustych prostokątów. I choć główna idea rozwiązania wszystkich tych wersji zadania jest taka sama, to różnią się one w szczegółach, które każdorazowo trzeba dopracować. Tutaj przedstawimy dość ogólną metodę, która pozwoli nam bardzo łatwo rozwiązywać podobne zadania. A mianowicie pokażemy, jak wyznaczyć w sumarycznym czasie liczbę pustych prostokątów rozmiaru dla wszystkich wartości
Pole leżące na przecięciu -tego wiersza i -tej kolumny kwadratu będzie miało współrzędne Przede wszystkim dla każdego pola będziemy chcieli znać liczbę niezabronionych pól leżących powyżej niego (włącznie z tym polem); oznaczymy tę wartość przez Całą tablicę łatwo obliczyć w czasie gdyż jeśli pole jest zabronione oraz w przeciwnym przypadku. Dla uproszczenia zakładamy również, że wszystkie pola poza kwadratem są zabronione i
Rozwiązanie podzielimy na faz; w -tej fazie będziemy rozpatrywać prostokąty, których dolny bok zawiera pola z -tego wiersza. Ustalmy pewne pole i rozważmy wszystkie puste prostokąty, których prawy dolny róg znajduje się na tym polu. Zbiór wszystkich pól, w których może znajdować się lewy górny róg takiego pustego prostokąta, jest obszarem, którego górny obrys składa się z odcinków idących w prawo i do góry. Taki obszar będziemy reprezentowali jako sumę minimalnej liczby rozłącznych prostokątów o coraz większych wysokościach, których dolne boki dotykają -tego wiersza (Rys. 2). Rozmiary kolejnych prostokątów będziemy trzymać na stosie, co umożliwi nam łatwe uaktualnianie tej reprezentacji, jeśli będziemy przeglądać kolumny od lewej do prawej.
Stos będziemy trzymać w tablicy (wysokości i szerokości -tego prostokąta na stosie to odpowiednio oraz ), a liczbę jego elementów w zmiennej Poniższy pseudokod pokazuje, jak uaktualnić zawartość stosu dla pola aby odpowiadała ona polu Dopóki wysokość najwyższego prostokąta na stosie jest co najmniej tak duża jak to usuwamy go ze stosu. Następnie wrzucamy na stos prostokąt rozmiaru gdzie jest sumaryczną długością wszystkich usuniętych prostokątów plus 1.
Zauważmy, że każdy prostokąt usuwany ze stosu jest kandydatem na największy pusty prostokąt. Co więcej, wystarczy, że rozpatrzymy tylko tych kandydatów. Ponieważ dla ustalonego liczba operacji wykonanych na stosie jest gdyż wrzucamy na niego elementów, zatem cały algorytm działa w czasie
Aby wyznaczyć liczbę pustych prostokątów wszystkich rozmiarów, postąpimy nieco sprytniej. Zachowamy następujący niezmiennik: jeśli znajdujemy się na polu to znaczy, że policzyliśmy już te puste prostokąty, których prawy dolny róg jest na polu dla i jednocześnie nie są one zawarte w obszarze dla pola
Zatem zwiększanie obszaru (poprzez wrzucanie nowych elementów na stos) nigdy nie będzie zmieniać liczności rozważonych pustych prostokątów, natomiast usuwanie elementów ze stosu może powodować konieczność aktualizacji. Wszystkie pola usuniętego prostokąta które znajdują się w odległości co najmniej od wiersza na pewno zostaną usunięte z obszaru (zakładamy przy tym, że ). Musimy zatem policzyć puste prostokąty zawarte w prostokącie które przestaną być w tym obszarze zawarte, czyli ich lewy górny róg jest jednym z pól, które zostaną usunięte z obszaru. Te prostokąty mają wysokości od do oraz szerokości od 1 do przy czym prostokątów o rozmiarze jest (Rys. 3).
Należy też uważnie rozważyć przypadek, gdy dla danego pola usuwamy więcej niż jeden element ze stosu. Wtedy szerokość aktualnie usuwanego elementu należy zwiększyć o szerokości usuniętych poprzednio (wygodnie jest tu korzystać z wprowadzonej już zmiennej ).
Jeśli zatem liczności pustych prostokątów będziemy zapisywać w tablicy (gdzie oznacza liczbę pustych prostokątów rozmiaru ), to mamy do rozwiązania następujący problem: dla danych liczb i chcemy szybko wykonywać operację zwiększania komórek o wartość dla oraz Nie możemy, oczywiście, pozwolić sobie na zrobienie tego zupełnie naiwnie. Zakładając, że początkowo w tablicy są same zera, zastosujemy następujący trik: do komórki wpiszemy 1, a do komórki wpiszemy Następnie niezależnie w każdej kolumnie tablicy obliczymy sumy prefiksowe. To spowoduje, że jedynki znajdą się w komórkach dla (a zniknie). Następnie w każdym wierszu obliczymy "trochę lepsze" sumy sufiksowe, które każdą jedynkę w komórce zamienią na ciąg liczb w komórkach Po takich manipulacjach tablica ma wartości zgodne z pojedynczą operacją zwiększania.
Nietrudno się przekonać, że jeśli chcemy wykonać więcej niż jedną operację zwiększania, to możemy najpierw zwiększyć wszystkie komórki o jeden i zmniejszyć odpowiadające im komórki o jeden, a dopiero na sam koniec jeden raz obliczyć powyższe sumy. Łącznie zajmie to czas a pseudokod algorytmu może wyglądać następująco:
Jako ćwiczenie dla Czytelnika proponujemy modyfikację tego algorytmu, aby dla każdego rozmiaru wyznaczał liczbę maksymalnych pustych prostokątów, czyli takich, które nie są zawarte w żadnym większym pustym prostokącie.