Omówienie zadania Walki robotów (I etap XXXII OI)
Rozwiązanie dla
W tym podzadaniu możemy zasymulować wszystkie możliwe gry i sprawdzić, czy w którejś uda nam się wyeliminować wszystkie roboty. Wykorzystamy w tym celu przeszukiwanie z nawrotami (ang. backtracking).
Załóżmy, że podczas symulacji turnieju dochodzimy do stanu, w którym pewna liczba robotów jest już wyeliminowana, a pozostałe dalej biorą udział w grze. Możemy wybrać pewną parę niewyeliminowanych robotów, rozegrać między nimi pojedynek, a następnie odrzucić roboty w nim wyeliminowane. W ten sposób otrzymujemy nowy stan i możemy kontynuować ten proces rekurencyjnie. Oczywiście dla danej sytuacji musimy rozpatrzeć wszystkie możliwe wybory par robotów do pojedynku.
Takie przeszukiwanie rozpoczynamy od stanu, w którym żaden robot nie jest wyeliminowany. Jeśli uda nam się dojść do stanu, w którym wszystkie roboty są wyeliminowane, to kończymy algorytm z odpowiedzią "TAK". W przeciwnym wypadku odpowiadamy "NIE".
Aby efektywnie zaimplementować powyższe rozwiązanie, możemy utrzymywać zbiór niewyeliminowanych robotów w postaci maski bitowej. Nasza symulacja będzie funkcją rekurencyjną przyjmującą maskę bitową jako argument. Wywołanie funkcji będzie polegać na przeiterowaniu się po wszystkich parach różnych zapalonych bitów w masce, rozegraniu pojedynku między odpowiadającymi im robotami i wywołaniu rekurencyjnym z maską bitową odpowiadającą stanowi po wyeliminowaniu robotów z pojedynku.
Złożoność naszego algorytmu możemy oszacować przez
Rozwiązanie dla
Powyższe rozwiązanie można przyspieszyć dzięki dokładniejszej analizie naszej funkcji rekurencyjnej. Wystarczy zauważyć, że zachowanie tej funkcji zależy jedynie od przekazanej jej maski bitowej reprezentującej stan gry. Innymi słowy, wywołanie tej funkcji z tą samą maską bitową zawsze da te same rezultaty. Wnioskujemy więc, że nie ma potrzeby wielokrotnego wykonywania funkcji dla tej samej maski. Możemy zatem zapamiętać dla wszystkich możliwych
Widzimy, że złożoność zmodyfikowanego rozwiązania to
Rozwiązanie dla
Aby przyspieszyć nasze rozwiązanie, nie możemy dalej symulować wszystkich możliwych gier. Skorzystamy z następującej interpretacji geometrycznej:

Patrząc na zadanie w ten sposób, możemy wprowadzić pojęcie zbioru majoryzującego robotów. Powiemy, że zbiór majoryzujący robotów to taki zbiór robotów, który spełnia następujące warunki:
- każda para robotów z tego zbioru eliminuje się wzajemnie przy pojedynku,
- dla każdego robota spoza zbioru majoryzującego istnieje robot ze zbioru majoryzującego, który go wyeliminuje i przy tym sam nie zostanie wyeliminowany.
Aby lepiej lepiej zilustrować tę definicję, rozważmy przykładową sytuację, w której mamy 7 robotów o wartościach
Analiza powyższego diagramu (oraz być może kilku innych dla innych sytuacji) pozwala nam w intuicyjny sposób sformułować pomocniczy lemat.
Lemat
Załóżmy, że roboty są posortowane względem wartości ich siły, czyli
Dowód lematu
Załóżmy, że roboty są posortowane względem wartości ich siły, czyli
Najpierw weźmy dwa różne numery robotów
Teraz weźmy numer robota
Co daje nam lemat?
Z lematu otrzymujemy prosty algorytm pozwalający nam znaleźć zbiór majoryzujący. Najpierw sortujemy roboty po ich sile, a następnie przeglądamy je w kolejności sił od największej do najmniejszej. Jeżeli przeglądany obecnie robot jest zwinniejszy od wszystkich przeglądanych dotychczas, to wrzucamy go do zbioru majoryzującego. Największą ze zwinności przeglądanych robotów możemy utrzymywać podczas ich przeglądania i aktualizować, biorąc maksimum przy każdym przeglądanym robocie. Taki algorytm można zaimplementować w złożoności
Dalej zauważmy, że znalezienie zbioru majoryzującego pozwala nam wyznaczyć odpowiedź. Po pierwsze, jeśli rozmiar zbioru majoryzującego jest parzysty, wówczas możemy wyeliminować z turnieju wszystkie roboty spoza tego zbioru w taki sposób, aby żaden robot ze zbioru majoryzującego nie został wyeliminowany. Wynika to wprost z definicji zbioru majoryzującego - dla każdego robota spoza tego zbioru istnieje robot z tego zbioru, który go eliminuje i sam nie zostaje wyeliminowany. Następnie pozostałe roboty ze zbioru majoryzującego dowolnie łączymy w pary i w takich parach przeprowadzamy walki. Założyliśmy, że dowolne dwa roboty ze zbioru majoryzującego eliminują się, a więc po takiej operacji nie pozostanie żaden robot. Otrzymujemy więc, że gdy rozmiar zbioru majoryzującego jest parzysty, odpowiedzią zawsze będzie "TAK".
Pozostało nam rozważyć przypadek, w którym rozmiar zbioru majoryzującego jest nieparzysty. W tym momencie, analizując przypadek wyżej, intuicja mogłaby podpowiadać, że w tym wypadku odpowiedzią zawsze będzie "NIE". Jednak nie zawsze jest to prawda. Zauważmy, że może istnieć pewien robot spoza zbioru majoryzującego (którego numer oznaczmy przez
Pozostało nam tylko pokazać, że roboty o numerach
Widzimy więc, że jeśli istnieje robot o numerze
Dalej zauważmy, że jeśli nie istnieje robot spoza zbioru majoryzującego, który byłby w stanie wyeliminować pewnego robota ze zbioru majoryzującego, to wówczas jedynym sposobem eliminowania robotów ze zbioru majoryzującego jest walka między dwoma robotami z tego zbioru. Z definicji tego zbioru wiemy, że taka walka kończy się wyeliminowaniem obu robotów. Skoro robotów w tym zbiorze jest nieparzyście wiele, a ich liczba może maleć tylko o dwa, to zawsze pozostanie nam przynajmniej jeden, a więc nigdy nie uda nam się wyeliminować wszystkich. Otrzymujemy, że wówczas odpowiedzią jest "NIE".
Algorytm
Na podstawie tych rozważań możemy opisać prosty algorytm rozwiązujący nasze zadanie. Najpierw wyznaczamy zbiór majoryzujący robotów w czasie
Pełne rozwiązanie
Od pełnego rozwiązania dzieli nas już tylko jedno spostrzeżenie. Zauważmy, że jako kandydatów spoza zbioru majoryzującego wystarczy rozważyć co najwyżej dwa roboty - te o maksymalnej wartości siły lub zwinności.
Dlaczego to prawda? Załóżmy, że
Udało nam się więc pokazać, że jeśli roboty o numerach
Dzięki tej obserwacji, możemy sformułować następujący algorytm: Najpierw sortujemy roboty względem wartości ich sił, następnie wyznaczamy zbiór majoryzujący i dalej, jeśli rozmiar tego zbioru jest parzysty, odpowiadamy "TAK" i kończymy. W przeciwnym przypadku szukamy robotów o maksymalnych wartościach siły i zwinności pośród robotów spoza tego zbioru. Jeśli takie nie istnieją (zbiór majoryzujący zawiera wszystkie roboty), to odpowiadamy "NIE" i kończymy. W przeciwnym przypadku sprawdzamy czy znalezione przez nas roboty (co najwyżej dwa) są w stanie wyeliminować jakiegoś robota ze zbioru majoryzującego. Jeśli okaże się, że tak, to odpowiadamy "TAK". W przeciwnym przypadku odpowiadamy "NIE". Takie rozwiązanie można zaimplementować w złożoności czasowej
Rozwiązanie alternatywne
Po dokładniejszej analizie okazuje się, że prawdziwa jest następująca obserwacja. Załóżmy, że roboty są posortowane po sile, czyli
Znalezenie takiego
Takie rozwiązanie oczywiście nie jest szybsze od rozwiązania opisanego powyżej - oba działają w złożoności
Opis przygotowano w ramach projektu "Mistrzostwa w Algorytmice i Programowaniu - Uczniowie" - zadania finansowanego ze środków Ministerstwa Cyfryzacji.
![]() |
![]() |
![]() |
![]() |
![]() |