To wyzwanie nie dotyczy gry Snake.
Wyobraź sobie węża 2d utworzonego przez narysowanie poziomej linii długości n
. W punktach całkowitych wzdłuż ciała, wąż ten może obracać ciało o 90 stopni. Jeśli na początku zdefiniujemy przód węża, który będzie po lewej stronie, obrót spowoduje przesunięcie tylnej części węża, a przednia część pozostanie na swoim miejscu. Powtarzając obroty, można uzyskać wiele różnych kształtów ciała węża.
Zasady
- Jedna część ciała węża nie może pokrywać się z inną.
- Musi być możliwe osiągnięcie ostatecznej orientacji bez nakładania się na siebie części ciała węża. Dwa dotykające się punkty są liczone jako pokrywające się w tym problemie.
- Uważam, że wąż i jego odwrotność mają ten sam kształt.
Zadanie
Jeśli chodzi o rotację, translację i symetrię lustrzaną, jaka jest łączna liczba różnych kształtów ciała węża, które można wykonać?
Przykład obrotu części ciała węży. Wyobraź sobie, n=10
że wąż jest w orientacji początkowej linii prostej. Teraz obróć w punkcie o 4
90 stopni w lewo. Dostajemy węża od 4
do 10
(ogona węża) leżącego pionowo, a węża od 0
do 4
leżącego poziomo. Wąż ma teraz jeden kąt prosty w ciele.
Oto kilka przykładów dzięki Martinowi Büttnerowi.
Zaczynamy od poziomego węża.
Teraz obracamy od pozycji 4.
W tej orientacji kończymy po rotacji.
Rozważmy teraz orientację innego węża.
Teraz możemy zobaczyć nielegalny ruch, w którym podczas rotacji wystąpiłoby nakładanie się.
Wynik
Twój wynik jest najwyższy, n
dla którego Twój kod może rozwiązać problem w mniej niż minutę na moim komputerze.
Kiedy nastąpi obrót, przesunie on wraz z nim o połowę węża. Musimy się martwić, czy którakolwiek z obracanych części może zachodzić na część węża podczas obrotu. Dla uproszczenia możemy założyć, że wąż ma szerokość zero. Można obracać tylko w określonym punkcie węża o maksymalnie 90 stopni w kierunku zgodnym z ruchem wskazówek zegara. Ponieważ nigdy nie można całkowicie złożyć węża na pół, ponieważ wymagałoby to dwóch obrotów w tym samym punkcie w tym samym kierunku.
Kształty, których nie można wykonać
Prostym przykładem kształtu, którego nie można wykonać, jest stolica T
. Bardziej wyrafinowana wersja to
(Dziękuję Haraldowi Hanche-Olsenowi za ten przykład)
W tym przykładzie wszystkie sąsiednie linie poziome są od siebie oddalone o 1, podobnie jak pionowe. W związku z tym nie ma legalnego przesunięcia z tej pozycji, a ponieważ problem jest odwracalny, nie ma zatem możliwości, aby dostać się z pozycji początkowej.
Języki i biblioteki
Możesz używać dowolnego języka, który ma swobodnie dostępny kompilator / tłumacz / etc. dla systemu Linux i dowolnych bibliotek, które są również bezpłatnie dostępne dla systemu Linux.
Moja maszyna Czasy zostaną uruchomione na moim komputerze. Jest to standardowa instalacja ubuntu na ośmiordzeniowym procesorze AMD FX-8350. Oznacza to również, że muszę być w stanie uruchomić Twój kod. W związku z tym korzystaj tylko z łatwo dostępnego bezpłatnego oprogramowania i dołącz pełne instrukcje, jak skompilować i uruchomić kod.
Odpowiedzi:
Python 3 - wynik tymczasowy: n = 11 (n = 13 z PyPy *)
Ponieważ w pierwszym tygodniu nie było odpowiedzi, oto przykład w Pythonie zachęcający do rywalizacji. Starałem się, aby był on wystarczająco czytelny, aby łatwo można było zidentyfikować nieefektywności, dając pomysły na inne odpowiedzi.
Podejście
Kod
(teraz z kilkoma doktrynami i stwierdzeniami po mojej niepoprawnej pierwszej próbie)
Wyniki
Na mojej maszynie najdłuższy wąż, który można obliczyć w czasie krótszym niż 1 minuta, ma długość 11 (lub długość 13 z PyPy *). Jest to oczywiście tylko wynik tymczasowy, dopóki nie dowiemy się, jaki jest oficjalny wynik z maszyny Lembika.
Dla porównania oto wyniki dla pierwszych kilku wartości n:
Daj mi znać, jeśli którekolwiek z nich okażą się nieprawidłowe.
Jeśli masz przykład aranżacji, która nie powinna być możliwa do rozwinięcia, możesz użyć funkcji,
neighbours(snake)
aby znaleźć aranżacje osiągalne w jednym kroku, jako test kodu.snake
jest krotką kierunków połączeń (0 dla wskazówek zegara, 1 dla linii prostych, 2 dla wskazówek zegara). Na przykład (1,1,1) jest prostym wężem o długości 4 (z 3 stawami).Wyobrażanie sobie
Aby zwizualizować węża, o którym myślisz, lub któregoś z węży powracających
neighbours
, możesz użyć tej funkcjidisplay(snake)
. To akceptuje krotkę jak inne funkcje, ale ponieważ nie jest używana przez program główny i dlatego nie musi być szybka, akceptuje również ciąg, dla twojej wygody.display((1,1,2,0))
jest równadisplay("1120")
Jak wspomina Lembik w komentarzach, moje wyniki są identyczne jak OEIS A037245, który nie uwzględnia skrzyżowań podczas rotacji. Wynika to z faktu, że dla pierwszych kilku wartości n nie ma różnicy - wszystkie kształty, które nie przecinają się, można osiągnąć, składając prostego węża. Poprawność kodu można sprawdzić, wywołując
neighbours()
węża, którego nie można rozwinąć bez skrzyżowania. Ponieważ nie ma sąsiadów, przyczyni się tylko do sekwencji OEIS, a nie do tej. Najmniejszy przykład, jaki znam, to ten wąż o długości 31, o którym wspomniał Lembik, dzięki Davidowi K :(1,1,1,1,2,1,2,1,1,1,1,1,1,2,1,1,1,2,1,1,2,2,1,0,1,0,1,1,1,1)
display('111121211111121112112210101111')
daje następujący wynik:Chciałbym usłyszeć od każdego, kto ma krótszy przykład bez sąsiadów. Podejrzewam, że najkrótszy taki przykład wyznaczy najmniejsze n, dla których dwie sekwencje różnią się.
* Zauważ, że każdy przyrost n zajmuje około 3 razy tyle, więc zwiększenie z n = 11 do n = 13 wymaga prawie 10 razy więcej czasu. Właśnie dlatego PyPy pozwala tylko zwiększać n o 2, nawet jeśli działa znacznie szybciej niż standardowy interpreter Pythona.
źródło
C ++ 11 - prawie działa :)
Po przeczytaniu tego artykułu zebrałem odrobinę mądrości od tego faceta, który najwyraźniej pracował przez 25 lat nad mniej skomplikowanym problemem liczenia ścieżek unikania na kwadratowej sieci.
Budowanie pliku wykonywalnego
Kompiluj z Używam MinGW pod Win7 z g ++ 4.8 dla kompilacji „linux”, więc przenośność nie jest gwarantowana w 100%.
g++ -O3 -std=c++11
Działa również (w pewnym sensie) ze standardowym projektem MSVC2013
Niezdefiniowanie
NDEBUG
pozwala uzyskać ślady wykonania algorytmu i podsumowanie znalezionych konfiguracji.Występy
z tablicami skrótów lub bez nich kompilator Microsoft działa marnie: kompilacja g ++ jest 3 razy szybsza .
Algorytm praktycznie nie wykorzystuje pamięci.
Ponieważ kontrola kolizji jest w przybliżeniu w O (n), czas obliczeń powinien być w O (nk n ), przy k nieco mniejszym niż 3.
W moim i3-2100@3,1 GHz, n = 17 zajmuje około 1:30 (około 2 milionów węże / minutę).
Nie skończyłem optymalizacji, ale nie spodziewałbym się więcej niż x3, więc w zasadzie mogę mieć nadzieję, że osiągnę n = 20 poniżej godziny lub n = 24 poniżej dnia.
Osiągnięcie pierwszego znanego nieugiętego kształtu (n = 31) zajęłoby od kilku lat do dekady, przy założeniu braku przerw w dostawie prądu.
Liczenie kształtów
N wielkość wąż N1 stawów.
Każde złącze może być lewe lub wygięte w lewo lub w prawo (3 możliwości).
Liczba możliwych składania wynosi zatem 3 N-1 .
Zderzenia nieco zmniejszą tę liczbę, więc faktyczna liczba jest bliska 2,7 N-1
Jednak wiele takich fałd prowadzi do identycznych kształtów.
dwa kształty są identyczne, jeśli istnieje obrót lub symetria, które mogą przekształcić jeden w drugi.
Zdefiniujmy segment jako dowolną prostą część złożonego korpusu.
Na przykład wąż wielkości 5 złożony na 2. stawie miałby 2 segmenty (jeden 2 jednostki długości i drugi 3 jednostki długości).
Pierwszy segment zostanie nazwany głową , a ostatni ogon .
Konwencjonalnie orientujemy głowę węży poziomo z ciałem skierowanym w prawo (jak na pierwszej figurze OP).
Daną liczbę oznaczamy za pomocą listy podpisanych długości segmentów, przy czym długości dodatnie wskazują na prawe fałdowanie, a ujemne na lewe.
Początkowa długość jest zgodna z konwencją.
Rozdzielanie segmentów i zagięć
Jeśli weźmiemy pod uwagę tylko różne sposoby podziału węża o długości N na segmenty, otrzymamy podział identyczny do składu N.
Przy użyciu tego samego algorytmu, jak przedstawiono na stronie wiki jest łatwy do wygenerowania wszystkich 2 n-1 możliwe przegrody węża.
Każda przegroda z kolei wygeneruje wszystkie możliwe zagięcia, stosując lewe lub prawe zagięcie do wszystkich swoich połączeń. Jedno takie składanie będzie nazywane konfiguracją .
Wszystkie możliwe partycje mogą być reprezentowane przez liczbę całkowitą N-1 bitów, przy czym każdy bit reprezentuje obecność połączenia. Nazwamy tę liczbę całkowitą generatorem .
Przycinanie partycji
Zauważając, że wygięcie danej przegrody od głowy w dół jest równoważne zgięciu symetrycznej przegrody od ogona w górę, możemy znaleźć wszystkie pary symetrycznych przegród i wyeliminować jedną z dwóch.
Generator symetrycznej partycji jest generatorem partycji zapisanym w odwrotnej kolejności bitów, który jest banalnie łatwy i tani do wykrycia.
To wyeliminuje prawie połowę możliwych partycji, wyjątkami są partycje z generatorami „palindromowymi”, które pozostają niezmienione przez odwrócenie bitów (na przykład 00100100).
Dbanie o poziome symetrie
Dzięki naszym konwencjom (wąż zaczyna wskazywać w prawo), pierwsze zagięcie zastosowane po prawej stronie stworzy rodzinę zagięć, które będą poziomymi symetriami od tych, które różnią się tylko pierwszym zgięciem.
Jeśli zdecydujemy, że pierwsze zakręt zawsze będzie po prawej stronie, eliminujemy wszystkie poziome symetrie za jednym razem.
Mycie palindromów
Te dwa cięcia są wydajne, ale niewystarczające, aby zająć się tymi nieznośnymi palindromami.
Najbardziej szczegółowa kontrola w ogólnym przypadku wygląda następująco:
rozważ konfigurację C z partycją palindromową.
Możemy sprawdzić każdą nową konfigurację z 3 innymi. Ponieważ jednak generujemy już tylko konfiguracje zaczynające się od skrętu w prawo, mamy tylko jedną możliwą symetrię do sprawdzenia:
To jedyna konfiguracja, którą możemy powielić.
Eliminowanie duplikatów bez przechowywania
Moje początkowe podejście polegało na przechowywaniu wszystkich konfiguracji w ogromnej tabeli mieszającej, aby wyeliminować duplikaty, sprawdzając obecność wcześniej obliczonej konfiguracji symetrycznej.
Dzięki wspomnianemu artykułowi stało się jasne, że ponieważ partycje i foldery są przechowywane jako pola bitowe, można je porównać jak każdą wartość liczbową.
Aby więc wyeliminować jednego członka pary symetrycznej, możesz po prostu porównać oba elementy i systematycznie zachować najmniejszy (lub największy, jak chcesz).
Zatem testowanie konfiguracji duplikacji sprowadza się do obliczenia symetrycznej partycji, a jeśli obie są identyczne, zwijanie. W ogóle nie jest potrzebna pamięć.
Kolejność generacji
Oczywiste jest, że kontrola kolizji będzie najbardziej czasochłonną częścią, więc zmniejszenie tych obliczeń jest znaczną oszczędnością czasu.
Możliwym rozwiązaniem jest posiadanie „węża ragdoll”, który rozpocznie się w płaskiej konfiguracji i będzie stopniowo wyginany, aby uniknąć ponownego obliczenia całej geometrii węża dla każdej możliwej konfiguracji.
Wybierając kolejność testowania konfiguracji, aby maksymalnie przechowywać ragdoll dla każdej łącznej liczby połączeń, możemy ograniczyć liczbę instancji do N-1.
Używam rekurencyjnego skanu sake od ogona w dół, dodając pojedynczy staw na każdym poziomie. Zatem nowa instancja ragdoll jest zbudowana na konfiguracji nadrzędnej, z jednym dodatkowym zgięciem.
Oznacza to, że zakręty są stosowane w kolejności sekwencyjnej, co wydaje się wystarczające, aby uniknąć kolizji we wszystkich przypadkach.
Po wykryciu kolizji zgięcia, które prowadzą do wykroczenia, są stosowane we wszystkich możliwych rozkazach, aż do znalezienia prawidłowego złożenia lub wyczerpania wszystkich kombinacji.
Kontrola statyczna
Zanim nawet pomyślałem o ruchomych częściach, bardziej efektywne było przetestowanie statycznego ostatecznego kształtu węża pod kątem skrzyżowań.
Odbywa się to poprzez narysowanie węża na siatce. Każdy możliwy punkt jest wykreślany od głowy w dół. Jeśli istnieje skrzyżowanie, co najmniej para punktów spadnie na to samo miejsce. Wymaga to dokładnie N wykresów dla dowolnej konfiguracji węża, dla stałego czasu O (N).
Główną zaletą tego podejścia jest to, że sam test statyczny po prostu wybierze prawidłowe sam unikające się ścieżki na kwadratowej sieci, co pozwala przetestować cały algorytm przez hamowanie dynamicznego wykrywania kolizji i upewnienie się, że znajdziemy prawidłową liczbę takich ścieżek.
Kontrola dynamiczna
Kiedy wąż składa się wokół jednego stawu, każdy obrócony segment zamiata obszar, którego kształt nie jest trywialny.
Oczywiście można sprawdzić kolizje, testując indywidualnie włączenie we wszystkich takich obszarach. Globalna kontrola byłaby bardziej wydajna, ale biorąc pod uwagę złożoność obszarów, o której nie mogę myśleć (oprócz użycia GPU do narysowania wszystkich obszarów i przeprowadzenia globalnej kontroli trafień).
Ponieważ test statyczny zajmuje się początkową i końcową pozycją każdego segmentu, musimy tylko sprawdzić przecięcia z łukami przeciągniętymi przez każdy obracający się segment.
Po interesującej dyskusji z trichoplaxem i odrobiną JavaScript, aby uzyskać orientację , wymyśliłem tę metodę:
Spróbuj zadzwonić w kilku słowach, jeśli zadzwonisz
(źródło: free.fr )
Dla każdego segmentu, który nie zawiera I , obszar przemiatania jest ograniczony przez 2 łuki (i 2 segmenty, które zostały już zajęte przez kontrolę statyczną).
Jeśli ja wchodzi w segmencie łuk zmieciony przez Muszę być również brane pod uwagę.
Oznacza to, że możemy sprawdzić każdy nieruchliwy segment w stosunku do każdego segmentu obrotowego za pomocą 2 lub 3 odcinków z łukiem
Użyłem geometrii wektorowej, aby całkowicie uniknąć funkcji trygonometrycznych.
Operacje wektorowe dają zwarty i (względnie) czytelny kod.
Przecięcie segmentu do łuku wymaga wektora zmiennoprzecinkowego, ale logika powinna być odporna na błędy zaokrąglania.
Znalazłem to eleganckie i wydajne rozwiązanie w niejasnym wpisie na forum. Zastanawiam się, dlaczego nie jest szerzej rozpowszechniany.
Czy to działa?
Hamowanie dynamicznego wykrywania kolizji powoduje powstanie prawidłowej liczby unikających ścieżek do n = 19, więc jestem pewien, że globalny układ działa.
Dynamiczne wykrywanie kolizji daje spójne wyniki, chociaż brakuje kontroli zgięć w innej kolejności (na razie).
W rezultacie program zlicza węże, które można zgiąć od głowy w dół (tj. Ze stawami złożonymi w kolejności rosnącej od głowy).
źródło