Niektóre zagadnienia z dziedziny informatyki dają się rozwiązać za pomocą
algorytmów symulujących określone procesy. Dostajemy w ten sposób poprawne odpowiedzi,
ale złożoność programu zależy od stopnia skomplikowania symulacji.
Jeśli czas działania programu nie jest dla nas satysfakcjonujący (na przykład dla dużych danych wejściowych),
to szukamy rozwiązania bardziej eleganckiego, choćby dla pewnych szczególnych przypadków.
W tym artykule skupimy się na tego typu problemie, pojawił się on już w Delcie cztery
lata temu, w artykule ,,Raz, dwa, trzy, wychodź ty!” Piotra Zarzyckiego
i został przedstawiony od strony matematycznej.
Tym razem skupimy się na jego informatycznym aspekcie.
Ostatnia osoba przy stole
W 2012 roku na Przedmiotowym Konkursie Informatycznym LOGIA pojawiło się
następujące zadanie:
Przy okrągłym stole uczestników spotkania siedzi na krzesłach
ponumerowanych od do Kolejno co -ta osoba wstaje i opuszcza
spotkanie. Zadaniem Antka jest wskazanie osoby, która pozostanie przy
stole jako ostatnia.
Naszym celem jest więc napisanie funkcji, która jako wejście przyjmuje liczby i a zwraca numer krzesła, które zostanie opuszczone jako ostatnie.
Jest to znane zadanie kombinatoryczne nazywane problemem Józefa Flawiusza.
Uczestnicy konkursu mogli rozwiązać to zadanie na kilka sposobów;
standardowe podejście to wykorzystanie symulacji wypraszania kolejnych osób ze
spotkania, co można zapisać następująco (w poniższym algorytmie przyjmujemy, że listę indeksujemy od ):
W tym podejściu przechowujemy numery osób na liście, którą w kolejnych krokach
skracamy, co odpowiada wypraszaniu kolejnych osób. Takie rozwiązanie, działające w złożoności czasowej (pojedyncze usunięcie elementu z listy ma pesymistyczną złożoność ), wystarczało do uzyskania maksymalnej liczby punktów.
Rozwiązanie to można nieco poprawić, uzyskując złożoność – zamiast usuwać elementy ze środka listy, przenosimy początkowych elementów listy na jej koniec, a potem usuwamy element, który aktualnie znajduje się na początku.
Warto się jednak zastanowić, czy zadania nie można rozwiązać zupełnie
inaczej. Idealnie byłoby, gdyby istniał taki wzór na pozycję ostatniej osoby,
który można wyliczyć za pomocą prostych operacji arytmetycznych.
Okazuje się, że nie jest to takie proste. Dla
istnieje jawny wzór, który za chwilę uzasadnimy, jednak dla sprawa się komplikuje.
Przypadek
Skupimy się teraz na przypadku – to znaczy, że co druga osoba wstaje i opuszcza
spotkanie. Niech oznacza pozycję (numer krzesła) osoby, która
zostanie przy stole jako ostatnia. Można zauważyć, że jeśli na początku
przy stole siedzi parzysta liczba osób, którą oznaczymy przez to po wyproszeniu pierwszych
z nich przy stole zostanie osób z kolejnymi numerami
nieparzystymi. Jest to taka sama sytuacja, jakby na początku przy stole
było osób, tylko że ich numery są podwojone i pomniejszone o jeden. Tę obserwację można zapisać w następujący sposób:
Kiedy na początku przy stole siedzi nieparzysta liczba osób (), po
wyproszeniu z nich przy stole zostanie osób ponumerowanych
kolejnymi liczbami nieparzystymi. Kolejną osobą, która zostanie
wyproszona, będzie ta na pozycji Wtedy przy stole będzie osób
o kolejnych numerach nieparzystych, zaczynając od – ponownie będzie to
taka sama sytuacja, jakby na początku było osób, tyle że tym razem ich
numery są podwojone i powiększone o jeden:
Korzystając ze wzorów i można zapisać wzór rekurencyjny:
Dla pierwszych szesnastu liczb całkowitych dodatnich wartości
zostały zapisane w tabelce.
|
1 |
1 |
3 |
1 |
3 |
5 |
7 |
1 |
3 |
5 |
7 |
9 |
11 |
13 |
15 |
1 |
W powyższej tabelce podzieliliśmy wartości funkcji na grupy o rozmiarach będących kolejnymi potęgami dwójki. W obrębie każdej grupy wartości funkcji są kolejnymi
liczbami nieparzystymi. Ta obserwacja sugeruje następującą hipotezę:
Prawdziwość tej hipotezy udowodnimy za pomocą indukcji
względem W przypadku bazowym, czyli dla jedyną możliwą wartością jest zero –
wystarczy więc sprawdzić tylko jeden przypadek: Weźmy teraz dowolne Zakładając prawdziwość tezy dla udowodnimy ją dla
Najpierw zauważmy, że dla nieparzystego możemy
skorzystać z założenia indukcyjnego, otrzymując:
Jeżeli natomiast jest parzyste, to analogicznie otrzymujemy:
To kończy dowód naszej hipotezy. Zauważmy, że po przekształceniu wzoru dostajemy wzór jawny:
który pozwala nam obliczać szybciej, niż wykonując symulację wypraszania kolejnych osób, bo w czasie Możemy się więc teraz skupić na przypadku
Przypadek
Przez będziemy oznaczać pozycję ostatniej osoby w problemie
Flawiusza dla osób i eliminacji co -tej osoby. Kiedy
wyeliminowana zostanie -ta osoba, w kręgu pozostanie osób.
Możemy teraz zamienić ich numery w ten sposób, że numeracja zaczynać się będzie
od osoby na pozycji (o jeden w prawo od wyeliminowanej).
Jeśli obliczymy pozycję ostatniej osoby dla mniejszego problemu
to możemy łatwo otrzymać sprawdzając, której osobie odpowiada przed zmianą numeracji.
Skoro zmiana numeracji polegała na przesunięciu numerów o
pozycji przy jednoczesnym usunięciu pozycji to wzór
rekurencyjny przyjmuje postać:
Konieczność odejmowania i późniejszego dodawania wynika z tego, że chcemy
uniknąć wyodrębniania szczególnego przypadku, kiedy .
Przy numeracji od zera wzór jest lekko zmodyfikowany i ma postać:
Zainteresowany Czytelnik zapewne zapyta, czy istnieje jawny wzór, gdy
Okazuje się, że odpowiedź jest twierdząca dla co pokazali w 1997 roku Lorenz Halbeisen i Norbert Hungerbühler. W celu obliczenia musimy najpierw znaleźć największą naturalną liczbę dla której gdzie jest pewną obliczalną stałą w przybliżeniu równą zaś oznacza standardowe zaokrąglanie do najbliższej liczby całkowitej.
Następnie definiujemy oraz Ponadto musimy zdefiniować jeszcze jedną stałą : jeśli (czyli zaokrąglaliśmy w górę), to kładziemy a w przeciwnym przypadku przyjmujemy Ostateczny wzór na szukaną funkcję to Dowód wyniku Halbeisena i Hungerbühlera jest dość skomplikowany. Co więcej, jeśli chcielibyśmy zaimplementować ich wzór tak, aby działał dla dowolnego to musimy umieć wyliczać wartość z dowolną dokładnością. Niestety stała jest zdefiniowana jako granica pewnego ciągu zbieżnego, a autorzy nie badają problemu złożoności jej obliczania. Nadmieńmy również, że dla odpowiedź na pytanie o istnienie wzoru jawnego nie jest nam wciąż znana.
Sam wzór rekurencyjny jednak jest już bardzo cenny, gdyż można go wykorzystać do obliczania
używając programowania dynamicznego. Podejście to przedstawia poniższy algorytm:
Skorzystaliśmy tutaj ze wzoru rekurencyjnego dla numeracji od więc na koniec musieliśmy dodać
Zauważmy, że algorytm programowania dynamicznego ma złożoność
Okazuje się, że zadania z konkursów programistycznych mogą
prowadzić do zagadnień o złożonym charakterze kombinatorycznym. To
ilustruje, jak różnorodny i nieoczywisty może być obszar nauki, który
ukrywa się za pozornie prostymi problemami.