Nonogram to japońska gra logiczna, w której celem jest narysowanie czarno-białego obrazu według listy przyległych regionów, takich jak:
Zdefiniuj nieograficzną wielkość wiersza lub kolumny, aby była liczbą sąsiadujących czarnych obszarów w tym rzędzie lub kolumnie. Na przykład górny rząd ma nieograficzną wielkość 1, ponieważ w tym rzędzie jest jeden obszar 2 kwadratów. Ósmy rząd ma nieograficzną wielkość 3, ponieważ ma 2, 2, 1.
Pusty wiersz lub kolumna ma nieograficzną wielkość 0.
Twoim zadaniem jest napisanie programu, który pobiera siatkę rozwiązania dla nonogramu i tworzy siatkę rozwiązania z jak najmniejszą liczbą wypełnionych kwadratów, w których każdy wiersz i kolumna ma ten sam nonograficzny magnutid jak podana siatka rozwiązania.
Na przykład siatka nonogramowa z wypełnionymi wszystkimi kwadratami ma nieograficzną wielkość 1 w każdym rzędzie lub kolumnie:
Tę samą nieograficzną wielkość można osiągnąć, po prostu ukośnym paskiem przechodzącym przez siatkę, co dramatycznie zmniejsza liczbę wypełnionych kwadratów:
Twój program otrzyma dane wejściowe składające się z 50 000 wierszy z tego pliku (1,32 MB pliku tekstowego tar.gz; 2,15 MB rozpakowanego), z których każdy reprezentuje pojedynczą siatkę rozwiązania 16 x 16 nonogram z losowo (80% czarnymi) wypełnionymi kwadratami, oraz wyprowadza kolejne 50 000 linii, z których każda zawiera zoptymalizowaną siatkę rozwiązań dla odpowiedniej siatki wejściowej.
Każda siatka jest reprezentowana jako ciąg base64 z 43 znakami (kodowanie kwadratów od lewej do prawej, a następnie od góry do dołu), a Twój program będzie musiał zwrócić swój wynik w tym samym formacie. Na przykład pierwsza siatka w pliku to E/lu/+7/f/3rp//f799xn/9//2mv//nvj/bt/yc9/40=
i renderuje jako:
Siatka zaczyna się od E
mapowania 000100
, więc wszystkie sześć pierwszych komórek w górnym rzędzie są białe, z wyjątkiem czwartej. Następną postacią jest /
mapa 111111
, na którą kolejne 6 komórek jest czarnych - i tak dalej.
Twój program musi faktycznie zwrócić siatkę rozwiązania o prawidłowej wielkości nieograficznej dla każdego z 50 000 przypadków testowych. Dopuszczalne jest zwrócenie tej samej siatki co danych wejściowych, jeśli nie znaleziono nic lepszego.
Zwycięzcą jest program zwracający najmniejszą liczbę wypełnionych kwadratów (w dowolnym języku), przy czym krótszym kodem jest rozstrzygający.
Aktualna tablica wyników:
- 3,637,260 - Sleafar, Java
- 7 270 894 - flawr, Matlab
- 10 239 288 - Joe Z., Bash
źródło
Odpowiedzi:
Python 2 i PuLP - 2 644 648 kwadratów (optymalnie zminimalizowane); 10 753 553 kwadraty (optymalnie zmaksymalizowane)
Minimalnie grał w golfa do 1152 bajtów
(Uwaga: mocno wcięte linie zaczynają się od tabulatorów, a nie spacji.)
Przykładowe dane wyjściowe: https://drive.google.com/file/d/0B-0NVE9E8UJiX3IyQkJZVk82Vkk/view?usp=sharing
Okazuje się, że takie problemy można łatwo przekształcić w całkowite programy liniowe i potrzebowałem podstawowego problemu, aby nauczyć się korzystać z PuLP - interfejsu python dla różnych programów LP - do własnego projektu. Okazuje się również, że PuLP jest niezwykle łatwy w użyciu, a niegodziwy konstruktor LP działał idealnie przy pierwszym uruchomieniu.
Dwie miłe rzeczy związane z zatrudnieniem rozgałęzionego solwera IP do ciężkiej pracy w rozwiązaniu tego problemu (oprócz konieczności implementacji rozgałęzionego i związanego solwera) to:
Jak korzystać z tego programu
Najpierw musisz zainstalować PuLP.
pip install pulp
powinien załatwić sprawę, jeśli masz zainstalowany pip.Następnie musisz umieścić w pliku o nazwie „c”: https://drive.google.com/file/d/0B-0NVE9E8UJiNFdmYlk1aV9aYzQ/view?usp=sharing
Następnie uruchom ten program w dowolnej późniejszej wersji Pythona 2 z tego samego katalogu. W niecały dzień otrzymasz plik o nazwie „s”, który zawiera 50 000 rozwiązanych nieogramowych siatek (w czytelnym formacie), z których każda zawiera podaną liczbę wypełnionych kwadratów.
Jeśli zamiast tego chcesz zmaksymalizować liczbę wypełnionych kwadratów, zmień
LpMinimize
linię 8 naLpMaximize
zamiast. Otrzymasz wynik bardzo podobny do tego: https://drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=sharingFormat wejściowy
Ten program używa zmodyfikowanego formatu wejściowego, ponieważ Joe Z. powiedział, że będziemy mogli ponownie zakodować format wejściowy, jeśli chcemy w komentarzu do OP. Kliknij powyższy link, aby zobaczyć, jak to wygląda. Składa się z 10000 linii, z których każda zawiera 16 cyfr. Linie parzyste to wielkości wierszy danego wystąpienia, a linie nieparzyste to wielkości kolumn tego samego wystąpienia, co linia nad nimi. Ten plik został wygenerowany przez następujący program:
(Ten program do ponownego kodowania dał mi również dodatkową okazję do przetestowania mojej niestandardowej klasy BitQueue, którą utworzyłem dla tego samego projektu wspomnianego powyżej. Jest to po prostu kolejka, do której dane mogą być wypychane jako sekwencje bitów LUB bajtów i z których dane mogą może być wyświetlany po jednym bajcie lub bajcie naraz. W tym przypadku działało idealnie).
Ponownie zakodowałem dane wejściowe z konkretnego powodu, że do zbudowania ILP dodatkowe informacje o siatkach, które zostały użyte do wygenerowania wielkości, są całkowicie bezużyteczne. Wielkości są jedynymi ograniczeniami, więc wielkości są wszystkim, do czego potrzebowałem dostępu.
Niegolfowany konstruktor ILP
Jest to program, który faktycznie stworzył „przykładowe wyjście” połączone powyżej. Stąd wyjątkowo długie sznurki na końcu każdej siatki, które obciąłem podczas gry w golfa. (Wersja do gry w golfa powinna dawać identyczne wyniki, bez słów
"Filled squares for "
)Jak to działa
Używam siatki 18 x 18, a środkowa część 16 x 16 jest właściwym rozwiązaniem układanki.
cells
to ta siatka. Pierwszy wiersz tworzy 324 zmienne binarne: „cell_0_0”, „cell_0_1” i tak dalej. Tworzę również siatki „przestrzeni” pomiędzy i wokół komórek w części rozwiązania siatki.rowseps
wskazuje na 289 zmiennych, które symbolizują spacje oddzielające komórki w poziomie, podczas gdycolseps
podobnie wskazuje na zmienne, które oznaczają spacje oddzielające komórki w pionie. Oto schemat Unicode:W
0
y i□
y to wartości binarne śledzone przezcell
zmienne,|
a wartości binarne są śledzone przezrowsep
wartości zmiennych i-
y to wartości binarne śledzone przezcolsep
zmiennych.To jest funkcja celu. Tylko suma wszystkich
cell
zmiennych. Ponieważ są to zmienne binarne, jest to dokładnie liczba wypełnionych kwadratów w rozwiązaniu.To po prostu ustawia komórki wokół zewnętrznej krawędzi siatki na zero (dlatego przedstawiłem je jako zera powyżej). Jest to najbardziej celowy sposób śledzenia liczby „bloków” komórek, które są wypełnione, ponieważ zapewnia, że każdej zmianie z niewypełnionej na wypełnioną (przechodzącej przez kolumnę lub wiersz) odpowiada odpowiednia zmiana z wypełnionej na niewypełnioną (i odwrotnie ), nawet jeśli pierwsza lub ostatnia komórka w wierszu jest wypełniona. Jest to jedyny powód, dla którego warto zastosować siatkę 18x18. To nie jedyny sposób na liczenie bloków, ale myślę, że jest najprostszy.
To jest prawdziwe mięso logiki ILP. Zasadniczo wymaga, aby każda komórka (inna niż w pierwszym rzędzie i kolumnie) była logicznym xor komórki i separatora bezpośrednio po lewej stronie w swoim rzędzie i bezpośrednio nad nią w kolumnie. Dostałem ograniczenia, które symulują xor w programie liczb całkowitych {0,1} z tej wspaniałej odpowiedzi: /cs//a/12118/44289
Aby wyjaśnić nieco więcej: to ograniczenie xor sprawia, że separatory mogą mieć wartość 1 wtedy i tylko wtedy, gdy znajdują się między komórkami, które są 0 i 1 (oznaczając zmianę z niewypełnionego na wypełnione lub odwrotnie). Zatem w wierszu lub kolumnie będzie dokładnie dwa razy więcej separatorów 1-wartościowych niż liczba bloków w tym wierszu lub kolumnie. Innymi słowy, suma separatorów w danym rzędzie lub kolumnie jest dokładnie dwa razy większa niż wielkość tego rzędu / kolumny. Stąd następujące ograniczenia:
I to tyle. Reszta prosi tylko domyślny solver o rozwiązanie ILP, a następnie formatuje powstałe rozwiązanie, zapisując je do pliku.
źródło
Java,
6093092 4332656 3637260 kwadratów (zminimalizowane),10567550 10567691 10568746 kwadratów (zmaksymalizowane)Oba warianty programu wielokrotnie wykonują operacje na siatce źródłowej, bez zmiany wielkości.
Minimizer
kurczyć się()
Jeśli czarny kwadrat ma 2 białych sąsiadów i 2 czarnych sąsiadów pod kątem 90 °, można go zastąpić białym kwadratem.
moveLine ()
W konfiguracjach takich jak powyżej czarna linia może być przesunięta w prawo. Odbywa się to wielokrotnie dla wszystkich 4 kierunków linii zgodnie z ruchem wskazówek zegara i przeciwnie do ruchu wskazówek zegara, aby otworzyć nowe możliwości kurczenia się.
Maksymalizator
Odkomentuj linię
main()
i skomentuj linię powyżej tej wersji.rosnąć()
Jeśli biały kwadrat ma 2 białych sąsiadów i 2 czarnych sąsiadów pod kątem 90 °, można go zastąpić czarnym kwadratem.
moveLine ()
Taki sam jak w Minimizerze.
Źródło
źródło
Bash - 10 239 288 kwadratów
Jako rozwiązanie referencyjne na ostatnim miejscu:
Ponieważ program może zwrócić tę samą siatkę, jeśli nie może znaleźć lepszego rozwiązania, wydrukowanie całego pliku jest również poprawne.
W pliku testowym znajduje się łącznie 10 239 288 czarnych kwadratów, co jest bardzo zbliżone do 10 240 000, których można oczekiwać od 80% kwadratów wypełnionych z 50 000 siatek z 256 kwadratami każdy. Jak zwykle w przypadku pytań dotyczących akumulatora testowego, wybrałem liczbę przypadków testowych z oczekiwaniem, że optymalne wyniki będą w okolicach 2 milionów, chociaż podejrzewam, że tym razem wyniki będą bliższe 4 lub 5 milionów .
Jeśli komuś uda się stworzyć rozwiązanie maksymalizujące czarne kwadraty zamiast je minimalizować i uda się uzyskać ponad 10 240 000, mogę rozważyć przyznanie mu nagrody.
źródło
Matlab, 7270894 kwadraty (~ 71% oryginału)
Pomysł jest prostym, powtarzającym się chciwym wyszukiwaniem: dla każdego czarnego kwadratu spróbuj ustawić go na biały bez zmiany wielkości nieograficznych. Powtórz to dwa razy. (Można osiągnąć znacznie lepsze wyniki przy większej liczbie powtórzeń, ale nie za darmo: Daje to odpowiednio dłuższy czas działania. Teraz było to około 80 minut. Zrobiłbym to, gdybyśmy nie musieli obliczać wszystkich 50 000 przypadków testowych ...)
Tutaj kod (jak zwykle każda funkcja w osobnym pliku).
źródło