Rozważ kwadratowy wykres siatki n na n, który wygląda tak.
Należy zauważyć, że ten wykres to 11 na 11 .
W dowolnym momencie mężczyzna stoi na skrzyżowaniu i porusza się tylko pionowo lub poziomo, krok po kroku, do następnego skrzyżowania. Niestety, wypił trochę za dużo, więc wybiera losowy kierunek z maksymalnie 4 możliwych kierunków (góra, dół, lewo, prawo). Jest to do 4, jak gdyby stał przy ścianie, ma oczywiście tylko 3 opcje, aw rogu ma tylko 2.
Zaczyna w lewym dolnym rogu, a jego celem jest powrót do domu, który jest w prawym górnym rogu. Czas to po prostu liczba kroków, które trzeba mu podjąć.
Jesteś jednak złośliwym przeciwnikiem, który chce, aby wracał do domu tak wolno, jak to możliwe. Możesz usunąć dowolną liczbę krawędzi z wykresu w dowolnym momencie podczas jego spaceru. Jedynym ograniczeniem jest to, że zawsze musisz zostawić mu drogę do powrotu do domu i nie możesz usunąć krawędzi, z której już skorzystał.
Wyzwanie polega na tym, aby wymyślić tak złośliwego przeciwnika, jak to możliwe, a następnie przetestować go na wykresie
100 na 10020 na 20 za pomocą przypadkowego pijanego piechura. Twój wynik to po prostu średni czas, jaki zajmuje losowemu piechurowi powrót do domu powyżej101000 biegów.
Możesz używać dowolnego języka i bibliotek, które lubisz, o ile są one swobodnie dostępne i łatwe do zainstalowania w systemie Linux.
Co muszę wdrożyć?
Powinieneś zaimplementować kod dla chodzika losowego, a także dla przeciwnika, a kod powinien być połączony, aby wynik po uruchomieniu był po prostu średnią z 1000 przebiegów przy użyciu kodu przeciwnika. Losowy kod walkera powinien być bardzo prosty do napisania, ponieważ po prostu wybiera spośród (x-1, y), (x + 1, y), (x, y-1) i (x, y + 1), upewniając się, że żaden z nich nie został usunięty lub nie jest poza zasięgiem.
Kod przeciwnika jest oczywiście trudniejszy i musi również pamiętać, które krawędzie pijak już przeszedł, aby nie próbował go usunąć i upewnić się, że dla pijaka jest jeszcze droga do domu, która jest nieco trudniejsza zrobić szybko.
Uzupełnienie 10 biegów to za mało, ale nie chciałem karać ludzi, którym udało się zrobić naprawdę długie spacery. Teraz zwiększyłem go do 1000 z powodu popularnej prośby. Jeśli jednak spacer jest tak długi, że nie możesz wykonać 1000 biegów w realistycznym czasie, po prostu zgłoś maksymalną liczbę biegów, jaką możesz.
Tabela najlepszych wyników dla 100 na 100.
- 976124.754 firmy Optimizer.
- 103000363.218 autor: Peter Taylor.
Edycja 1. Zmieniłem rozmiar wykresu na 20 na 20, aby pomóc w czasie wykonywania testów ludzi. Zrobię nowy wysoki wynik tabeli dla tego rozmiaru, gdy ludzie będą przedstawiać wyniki.
Tabela najlepszych wyników dla 20 na 20.
230,794.38 (100k runs) by justhalf
227,934 by Sparr
213,000 (approx) by Peter Taylor
199,094.3 by stokastic
188,000 (approx) by James_pic
64,281 by Geobits
Odpowiedzi:
230 794,38 na biegach 20 x 20, 100 000
Ostatnia aktualizacja: W końcu zbudowałem idealne dynamiczne rozwiązanie 2-ścieżkowe. Powiedziałem doskonale, ponieważ poprzednia wersja faktycznie nie jest symetryczna, łatwiej było uzyskać dłuższą ścieżkę, jeśli pijak przeszedł jedną ścieżkę nad drugą. Obecny jest symetryczny, dzięki czemu może uzyskać wyższą oczekiwaną liczbę kroków. Po kilku próbach wydaje się, że wynosi około 230 tys., Co oznacza poprawę w stosunku do poprzedniego, wynoszącą około 228 tys. Ale statystycznie rzecz biorąc liczby te wciąż mieszczą się w swoich ogromnych odchyleniach, więc nie twierdzę, że jest to znacznie lepsze, ale uważam, że powinno to być lepsze niż poprzednia wersja.
Kod znajduje się na dole tego postu. Jest aktualizowany, dzięki czemu jest znacznie szybszy niż poprzednia wersja, wykonując 1000 uruchomień w 23s.
Poniżej znajduje się przykładowy przebieg i przykładowy labirynt:
Poprzednie zgłoszenia
Wreszcie mogę dopasować wynik Sparra! = D.
Opierając się na moich wcześniejszych eksperymentach (patrz na dole tego postu), najlepszą strategią jest posiadanie podwójnej ścieżki i zamknięcie jednej, gdy pijak dotrze do dowolnego z nich, a zmienna wynika z tego, jak dobrze możemy dynamicznie przewidzieć, dokąd pójdzie pijak zwiększyć szansę, że dostanie się na dłuższą ścieżkę.
Tak więc w oparciu o moją
DOUBLE_PATH
strategię zbudowałem kolejny, który zmienia labirynt (mójDOUBLE_PATH
labirynt można łatwo modyfikować) w zależności od ruchu pijaka. Gdy będzie podążał ścieżką z więcej niż jedną dostępną opcją, zamknę ścieżki tak, aby pozostawić tylko dwie możliwe opcje (jedna, z której przyszedł, druga nietrawerowana).Brzmi to podobnie do tego, co osiągnął Sparr, jak pokazuje wynik. Różnica w stosunku do jego jest zbyt mała, aby uznać ją za lepszą, ale powiedziałbym, że moje podejście jest bardziej dynamiczne niż on, ponieważ mój labirynt jest bardziej modyfikowalny niż Sparra =)
Wynik z przykładowym labiryntem końcowym:
Sekcja Eksperymentów
Najlepsza okazuje się ta sama strategia co stokastic, jestem dumny z eksperymentowania przy użyciu różnych strategii i drukowania dobrych wyników :)
Każdy z drukowanych labiryntów poniżej jest ostatnim labiryntem po dotarciu pijaka do domu, więc mogą się nieznacznie różnić od biegu do biegu ze względu na losowość ruchów pijaka i dynamikę przeciwnika.
Opiszę każdą strategię:
Jedna ścieżka
Jest to najprostsze podejście, które utworzy jedną ścieżkę od wejścia do wyjścia.
Wyspa (poziom 0)
To podejście próbuje złapać pijaka w prawie odizolowanej wyspie. Nie działa tak dobrze, jak się spodziewałem, ale jest to jeden z moich pierwszych pomysłów, więc go uwzględniam.
Do wyjścia prowadzą dwie ścieżki, a gdy pijak zbliża się do jednej z nich, przeciwnik zamyka ją, zmuszając go do znalezienia drugiego wyjścia (i prawdopodobnie ponownie uwięziony na wyspie)
Podwójna ścieżka
Jest to najczęściej dyskutowana strategia, polegająca na posiadaniu dwóch ścieżek o równej długości do wyjścia i zamykaniu jednej z nich, gdy pijak zbliży się do jednej z nich.
Wyspa (poziom 1)
Zainspirowani wieloma ścieżkami wyspy i wysoką liczbą kroków w pojedynczej ścieżce, łączymy wyspę z wyjściem i wykonujemy labirynt pojedynczej ścieżki na wyspie, tworząc w sumie trzy ścieżki wyjścia i podobnie jak w poprzednim przypadku, zamknij dowolną z wyjdź, gdy pijak się zbliży.
Działa to nieco lepiej niż czysta pojedyncza ścieżka, ale nadal nie pokonuje podwójnej ścieżki.
Wyspa (poziom 2)
Próbując rozwinąć poprzedni pomysł, stworzyłem zagnieżdżoną wyspę, tworząc w sumie pięć ścieżek, ale wydaje się, że to nie działa dobrze.
Wyspa (poziom 3)
Zauważając, że podwójna ścieżka faktycznie działa lepiej niż pojedyncza ścieżka, zróbmy wyspę podwójną ścieżką!
Rezultatem jest poprawa w stosunku do wyspy (poziom 1), ale wciąż nie pokonuje podwójnej ścieżki.
Dla porównania wynik dla podwójnej ścieżki wielkości wyspy wynosi średnio 131 134,42 ruchów. To dodaje dość znacznej liczby ruchów (około 40k), ale nie wystarcza, aby pokonać podwójną ścieżkę.
Wyspa (poziom 4)
Ponownie, eksperymentowanie z zagnieżdżoną wyspą i znowu to nie działa tak dobrze.
Wniosek
Podsumowując, dowodzi to, że posiadanie pojedynczej długiej ścieżki od aktualnej pozycji pijaka do wyjścia działa najlepiej, co osiąga się dzięki strategii podwójnej ścieżki, ponieważ po zamknięciu wyjścia pijak będzie musiał przebyć maksymalną możliwą odległość, aby dostać się do wyjście.
To dalsze wskazówki, że podstawową strategią powinna nadal być podwójna ścieżka, i możemy jedynie modyfikować dynamikę tworzenia ścieżek, co zrobiło Sparr. Uważam więc, że jego strategia jest najlepsza!
Kod
źródło
227934 (20x20)
Moja trzecia próba. Stosuje to samo ogólne podejście co @stokastic z dwiema ścieżkami do wyjścia. Kiedy piechur dotrze do końca jednej ścieżki, zamyka się, wymagając od niego powrotu, aby dostać się do końca drugiej ścieżki w celu wyjścia. Moje ulepszenie polega na generowaniu ścieżek wraz z postępem piechura, aby każda ścieżka, którą posunie się dalej w pierwszej połowie procesu, skończy się dłuższą drogą niż druga.
wyjście (z czasem):
przykład mojego labiryntu z mniej więcej równymi połówkami ścieżki, pokazujący ścieżkę w lewo / w dół odciętą od wyjścia (prawy dolny róg):
PS: Zdaję sobie sprawę z niewielkiej poprawy tego algorytmu, która wymaga bardziej sprytnego kodu, aby wygenerować inny kształt dla dwóch ścieżek, klatek schodowych zamiast spójnych wysokości zygzaków.
źródło
135 488 307,9 dla 98 x 98
199094.3 dla 20x20
Wdrożyłem rozwiązanie, które tworzy dwie ścieżki do mety i zamyka dokładnie jedną z nich, gdy dotrze do niej pijak. To symuluje długość ścieżki, która będzie co najmniej 1,5 raza większa niż długość pojedynczej ścieżki od początku do końca. Po 27 biegach trafiłem średnio około 135 milionów. Niestety zajmuje to kilka minut na spacer, więc będę musiał go uruchomić przez kilka następnych godzin. Jedno zastrzeżenie - mój generator podwójnych ścieżek działa tylko wtedy, gdy rozmiar wykresu ma postać 4 * n + 2, co oznacza, że najbliższe 100 to 102 lub 98. Zamierzam opublikować wyniki przy użyciu 98, czego oczekuję aby nadal przewyższać metodę ścieżki zygzakowej. Później będę pracować nad lepszym systemem ścieżki. Obecnie wyniki wyjściowe mają postać (numSteps, currentA Average) po każdym marszu.
EDYCJA: naprawiono, więc kod działa teraz na rozmiarach wykresów, które są dowolną wielokrotnością liczby 2, a nie 4 * n + 2.
Kod: (dodaj argument „True” do konstruktora walkera w linii 187 w celu rysowania wykresu przez żółwia).
Surowe dane: (bieżąca liczba kroków, średnia bieżąca)
źródło
4-ścieżkowe podejście, 213 tys
Podejście jednościeżkowe to
i zdobywa średnią
N^2
.Podejście dwutorowe jest
ale kiedy pierwszy raz pijak znajdzie się w zasięgu punktu końcowego, zostaje odcięty:
Uzyskuje średnią
(N/2)^2 + N^2
.Podejście czterościeżkowe wykorzystuje dwa cięcia:
Załóżmy, że zewnętrzna pętla ma długość,
xN
a wewnętrzna pętla jest długa(1-x)N
. Dla uproszczenia dokonam normalizacjiN=1
.Od początku do pierwszego cięcia przeciętnie wynosi
(x/2)^2
. Od pierwszego cięcia do drugiego cięcia ma dwie opcje, długościx
i1-x
; daje to średnio(1-x)x^2 + x(1-x)^2 = x-x^2
. Wreszcie pozostała ścieżka daje1
. Tak więc całkowity wynik toN^2 (1 + x - 3/4 x^2)
.Początkowo założyłem, że utrzymanie dostępnych ścieżek o równej długości na każdym kroku byłoby optymalne, więc w moim początkowym podejściu wykorzystałem
x = 1/2
wynik1.3125 N^2
. Ale po wykonaniu powyższej analizy okazuje się, że optymalny podział daje sięx = 2/3
z wynikiem1.3333 N^2
.z kodem
źródło
f
doc
w twoim kodzie jestN/2
, czy to przeze
(id
) czy nie, prawda?N^2
nie jest2^N
. I tak, sprawienie, by ta dynamika sprawiła, że będzie najlepsza, wyzwanie polega na tym, jak uczynić ją dynamiczną, zachowując właściwość czterościeżkową. @PeterTaylor: Ładne zdjęcia!Eksperymentowałem z przecinaniem siatki prawie całkowicie w każdym
k
rzędzie. To skutecznie zamienia go na czymś podobnym do błądzenia losowego nak
przezN * N/k
siatkę. Najbardziej skuteczną opcją jest krojenie każdego rzędu, aby zmusić pijaka do zygzaka.Dla przypadku 20x20 (
SIZE=19
) mamz kodem
źródło
Dla tych, którzy nie chcą odkrywać koła od nowa
Nie martw się! Wymyślę to dla ciebie :)
Nawiasem mówiąc, jest to w Javie.
Stworzyłem klasę Walker, która zajmuje się chodzeniem losowo. Zawiera także przydatną metodę określania, czy ruch jest prawidłowy (jeśli został już wykonany).
Zakładam, że wszyscy sprytni ludzie mogą wymyślić losowe liczby dla konstruktora, zostawiłem to tobie, abyś mógł przetestować pewne przypadki. Również po prostu wywołaj funkcję walk (), aby (zgadłeś!), Aby pijak chodził (losowo).
Zaimplementuję funkcję canComeHome () kiedyś. Najlepiej po znalezieniu najlepszego sposobu na zrobienie tego.
źródło
previouslyTraveled.add(new int[]{x,y,move[0],move[1]})
powinien byćx+move[0]
iy+move[1]
.Width-1
IHeight-1
oraz nieskuteczność w sprawdzanie usunięte ścieżek. Zmodyfikowałem twój kod (z dodatkową funkcją do wydrukowania labiryntu). Jeśli uważasz, że to niestosowne, możesz je wycofać.Edge
nie działa poprawnieComparable<Edge>
. Jeśli chcesz, aby krawędzie porównywały się jako równe, nawet jeśli je odwrócisz, musisz również uwzględnić odwrócenie w przypadku nierówności. Najłatwiejszym sposobem jest zmiana konstruktora, aby zachować uporządkowane punkty.Comparable
?A
iB
ta sama krawędź jest odwrócona iC
jest inna, możesz dostaćA.compareTo(B) == B.compareTo(A) == 0
aleA.compareTo(C) < 0
iB.compareTo(C) > 0
.canComeHome()
)64.281
Aktualizacja od czasu zmiany siatki ze 100 x 100 na 20 x 20 (1000 testów). Wynik w 100x100 (100 testach) wynosił około 36 milionów.
Chociaż nie będzie to lepszy niż 1D, chciałem zagrać z pomysłem, który miałem.
Podstawową ideą jest podzielenie siatki na kwadratowe pokoje, z których każda prowadzi tylko jedna ścieżka. Otwarta ścieżka jest tym, co pijak zbliży się do ostatniego , co oznacza, że musi zbadać każde możliwe wyjście, tylko po to, by walnąć wszystkich oprócz jednego.
Po zabawie z rozmiarem pokoju doszedłem do tego samego wniosku co Peter, że zmniejszenie go na mniejsze jest lepsze. Najlepsze wyniki pochodzą z pokoju o wielkości 2.
Kod jest niechlujny, nie przejmuj się bałaganem. Możesz
SHOW
przełączyć przełącznik, aby na każdymSHOW_INT
kroku wyświetlał obraz ścieżek, dzięki czemu można go oglądać w akcji. Zakończony przebieg wygląda mniej więcej tak:(To jest obraz z poprzedniej siatki 100 x 100. 20 x 20 jest dokładnie taki, ale, no cóż, mniejszy. Poniższy kod został zaktualizowany dla nowego rozmiaru / serii).
źródło
188k, z 2 ścieżkami
Wszystkie najlepsze wpisy wydają się przyjmować podejście polegające na wygenerowaniu 2 ścieżek, a następnie odcięciu jednego, gdy pijak zbliża się do końca ścieżki. Nie sądzę, żebym mógł pobić wpis justhalfa, ale nie mogłem powstrzymać się od zastanowienia: dlaczego 2 ścieżki? Dlaczego nie 3, 5 lub 20?
TL; DR : 2 ścieżki wydają się być optymalne
Więc zrobiłem eksperyment. Opierając się na frameworku Stretch Maniac, napisałem wpis, aby przetestować różne liczby ścieżek. Możesz dostosować
featureSize
parametr, aby zmienić liczbę ścieżek. AfeatureSize
z 20 daje 1 ścieżkę, 10 daje 2 ścieżki, 7 daje 3, 5 daje 4 i tak dalej.Jest kilka optymalizacji, które mogłem zrobić, ale których nie zrobiłem, i nie obsługuje żadnej adaptacyjnej sztuczki, której używa justhalf.
Tak czy inaczej, oto wyniki dla różnych
featureSize
wartości:A oto mapa z 3 ścieżkami:
źródło
N
będzie to długość ścieżki (która jestn^2-1
), jedna ścieżka będzie średnio wymagałaN^2
ruchów, podczas gdy trzy ścieżki(N/3)^2 + (2N/3)^2 + (2N/3)^2 = N^2
plus pewna stosunkowo niewielka wartość, więc trzy ścieżki nie mają znaczącego zysku nad pojedynczą ścieżką, nie mówiąc już o podwójnej ścieżce. (Obliczenia oparte są na wyniku prawdopodobieństwa, który stwierdza, że losowy ruch na ścieżce 1-D długościN
wymaga przeciętnegoN^2
przemieszczania się z jednego końca na drugi.)131k (20x20)
Moja pierwsza próba polegała na usunięciu wszystkich poziomych krawędzi oprócz górnego i dolnego rzędu, a następnie za każdym razem, gdy piechur docierał do dolnej części kolumny, zdejmowałem krawędź przed nim, dopóki nie odwiedził dolnej części każdej kolumny i w końcu być w stanie dotrzeć do wyjścia. Spowodowało to średnio 1/8 tyle kroków, co podejście 1 Peter @ PeterTaylor.
Następnie postanowiłem spróbować czegoś bardziej okrężnego. Podzieliłem labirynt na szereg zagnieżdżonych pustych szewronów i wymagam od niego przemierzania obwodu każdego szewronu co najmniej 1,5 razy. Średni czas wynosi około 131 000 kroków.
źródło
Nic nie robić
Ponieważ mężczyzna porusza się losowo, można pomyśleć, że usunięcie dowolnego węzła tylko zwiększy jego szanse na powrót do domu w dłuższej perspektywie.
Po pierwsze, spójrzmy na przypadek jednowymiarowy, można to osiągnąć, usuwając węzły, dopóki nie skończy się kręta ścieżka, bez przerw i cykli, która odwiedza (prawie) każdy punkt siatki. Na
N x N
siatce maksymalna długość takiej ścieżki wynosiL = N*N - 2 + N%2
(98 dla siatki 10x10). Idąc ścieżką można opisać macierz przejścia wygenerowaną przezT1d
.(Niewielka asymetria utrudnia znalezienie rozwiązania analitycznego, z wyjątkiem bardzo małych lub nieskończonych matryc, ale otrzymujemy rozwiązanie numeryczne szybciej niż zajęłoby to przekątne matrycy).
Wektor stanu ma pojedynczy
1
w pozycji początkowej i późniejK
krokach(T1d**K) * state
daje rozkład prawdopodobieństwa bycia w pewnej odległości od początku (co jest równoważne uśrednieniu dla wszystkich2**K
możliwych spacerów wzdłuż ścieżki!)Uruchomienie symulacji dla
10*L**2
kroków i zapisanie ostatniego elementu wektora stanu po każdym kroku, co daje nam prawdopodobieństwo dotarcia do celu po określonej całkowitej liczbie kroków - skumulowanym rozkładzie prawdopodobieństwacd(t)
. Różnicowanie go daje nam prawdopodobieństwop
osiągnięcia celu dokładnie w określonym czasie. Aby znaleźć średni czas, który integrujemy,t*p(t) dt
średni czas do osiągnięcia celu jest proporcjonalny do
L**2
współczynnika, który idzie bardzo szybko do 1. Odchylenie standardowe jest prawie stałe i wynosi około 79% średniego czasu.Ten wykres pokazuje średni czas do osiągnięcia celu dla różnych długości ścieżek (odpowiadających rozmiarom siatki od 5 x 5 do 15 x 15)
Oto jak wygląda prawdopodobieństwo osiągnięcia celu. Druga krzywa wygląda na wypełnioną, ponieważ przy każdym nieparzystym czasie pozycja jest nieparzysta i dlatego nie może być w bramce.
Z tego wynika, że najlepiej sprawdza się tutaj zrównoważona strategia podwójnej ścieżki. W przypadku większych siatek, gdzie narzut związany z tworzeniem większej liczby ścieżek jest znikomy w porównaniu z ich rozmiarem, lepiej byłoby zwiększyć liczbę ścieżek, podobnie jak to opisał Peter Taylor, ale utrzymywać równowagę długości
Co jeśli nie usuniemy żadnych węzłów?
Wtedy mielibyśmy dwa razy więcej dostępnych węzłów, a także cztery możliwe kierunki zamiast dwóch. Wydaje się, że sprawia, że jest bardzo mało prawdopodobne, aby kiedykolwiek się dostać. Jednak symulacje wskazują inaczej, po zaledwie 100 kroków na 10x10 siatki człowiek jest całkiem prawdopodobne, aby osiągnąć swój cel, więc trappin go na wyspach jest daremną próbą, ponieważ jesteś handlowych potencjał
N**2
długie kręte ścieżki o średnim czasie ukończeniaN**4
przez wyspa przekazywanaN**2
krokamiźródło