To nie jest solver Sudoku ani sprawdzanie Sudoku.
Twoim wyzwaniem jest napisanie funkcji lub skryptu, który podając jako dane wejściowe rozmiar „bloku” łamigłówki 2D Sudoku (czyli 3 dla klasycznej planszy 9x9 , 4 dla planszy 16x16 itp.) Obliczy przybliżoną liczbę różnych łamigłówek (rozwiązań), które istnieją dla tego rozmiaru.
Na przykład, biorąc pod uwagę wejście 3, twój program powinien wypisać przybliżenie, z pożądaną precyzją, liczby 6,670,903,752,021,072,936,960, która jest znaną liczbą różnych łamigłówek Sudoku 9x9 , lub 5 472 730 538, biorąc pod uwagę różne symetrie. Twoje rozwiązanie powinno określać, czy symetrie są liczone, czy ignorowane.
„Pożądana precyzja” pozostaje niezdefiniowana: program może działać przez określony czas, a następnie wyświetlać wynik lub obliczyć go do określonej liczby cyfr znaczących, a nawet działać wiecznie, drukując coraz lepsze przybliżenia. Chodzi o to, że powinno być możliwe obliczenie wyniku z dowolną wymaganą precyzją, w skończonym czasie. (Zatem „42” nie jest akceptowalną odpowiedzią.) Dopuszczalne jest ograniczenie dokładności wyniku do dostępnych liczb zmiennoprzecinkowych maszyny.
Brak dostępu do zasobów online, brak przechowywania kodu źródłowego w nazwie pliku itp.
PS: Wiem, że jest to trudny problem (NP-zupełne, jeśli się nie mylę). Ale to pytanie wymaga jedynie przybliżonego, statystycznego rozwiązania. Na przykład możesz wypróbować losowe konfiguracje, które spełniają jedno (lub lepiej dwa) ograniczenia, obliczyć, ile z nich istnieje, a następnie sprawdzić, jak często otrzymujesz układankę, która spełnia wszystkie trzy ograniczenia. Będzie to działać w przyzwoitym czasie dla małych rozmiarów (z pewnością dla rozmiaru = 3 i ewentualnie 4), ale algorytm powinien być wystarczająco ogólny, aby działał dla dowolnego rozmiaru.
Najlepszy algorytm wygrywa.
PS2: Zmieniłem kod-golfa na kod-wyzwanie, aby lepiej odzwierciedlić trudność problemu i zachęcić do mądrzejszych rozwiązań, zamiast głupich, ale dobrze grających. Ale ponieważ najwyraźniej „najlepszy algorytm” jest niejasny, pozwól mi spróbować go poprawnie zdefiniować.
Biorąc pod uwagę wystarczającą ilość czasu i pomijając stałe czynniki (w tym szybkość procesora i szybkość interpretera), lub równoważnie, biorąc pod uwagę ich asymptotyczne zachowanie, które rozwiązanie uzyskałoby najszybszy dokładny wynik?
źródło
Odpowiedzi:
C ++
Przedstawię tutaj algorytm, zilustrowany przykładem przypadku 3x3. Teoretycznie można by go rozszerzyć na obudowę NxN, ale wymagałoby to znacznie mocniejszego komputera i / lub kilku genialnych poprawek. Będę wspominał o niektórych ulepszeniach w miarę przechodzenia.
Zanim przejdziemy dalej, zwróćmy uwagę na symetrie siatki Sudoku, czyli transformacje, które prowadzą do innej siatki w trywialny sposób. Dla wielkości bloku 3 symetrie są następujące:
Pozioma symetria
Symetria pionowa
Należy pamiętać, że poziome i pionowe odbicia siatki można uzyskać przez ich połączenie, więc nie trzeba ich liczyć. Należy wziąć pod uwagę jeszcze jedną symetrię przestrzenną, która jest transponowana, co jest czynnikiem
2
. Daje to całkowitą symetrię przestrzennąPotem jest inna, bardzo ważna symetria, zwana ponownym znakowaniem.
Całkowitej liczby rozwiązań nie można znaleźć po prostu przez pomnożenie liczby unikalnych dla symetrii rozwiązań przez tę liczbę, ponieważ istnieje liczba (mniej niż 1%) rozwiązań automorficznych. Oznacza to, że dla tych specjalnych rozwiązań istnieje operacja symetrii, która odwzorowuje je na siebie, lub wiele operacji symetrii, które odwzorowują je na to samo inne rozwiązanie.
Aby oszacować liczbę rozwiązań, podchodzę do problemu w 4 krokach:
1. Wypełnij tablicę
r[362880][12]
wszystkimi możliwymi kombinacjami liczb od 0 do 8. (to jest programowanie, i jest w C, więc nie będziemy używać od 1 do 9.) Jeśli jesteś bystry, zauważysz, że drugi indeks dolny wynosi 12, a nie 9. Jest tak, ponieważ robiąc to, pamiętając, że będziemy uważać to za „rząd”, obliczamy również trzy kolejne liczby całkowite,r[9,10,11] == 1<<a | 1<<b | 1<<c
gdzie 9,10,11 odnoszą się do pierwszego, drugiego i trzeciego stosu a a, b, c są trzema liczbami obecnymi w każdym stosie dla tego rzędu.2. Wypełnij tablicę
b
wszystkimi możliwymi rozwiązaniami pasma 3 rzędów. Aby zachować ten stosunkowo mały rozmiar, należy uwzględniać tylko te rozwiązania, w których górny rząd to 012 345 678. Robię to brutalną siłą, generując wszystkie możliwe środkowe rzędy i ANDingr[0][10,11,12]
zr[i][10,11,12]
. Każda wartość dodatnia oznacza, że na tym samym kwadracie znajdują się dwie identyczne liczby, a pasmo jest nieprawidłowe. Gdy istnieje poprawna kombinacja dla pierwszych dwóch wierszy, przeszukuję trzeci (dolny) rząd tą samą techniką.Zwymiarowałem tablicę na b [2000000] [9], ale program znajduje tylko 1306368 rozwiązań. Nie wiedziałem, ile ich było, więc zostawiłem taki wymiar tablicy. To właściwie tylko połowa możliwych rozwiązań dla pojedynczego pasma (zweryfikowane na wikipedii), ponieważ skanuję tylko trzeci rząd od bieżącej wartości w
i
górę. Pozostałą połowę rozwiązań można znaleźć trywialnie, wymieniając drugi i trzeci rząd.Sposób przechowywania informacji w tablicy
b
jest na początku nieco mylący. zamiast używać każdej liczby całkowitej do przechowywania liczb0..8
znalezionych na danej pozycji, tutaj każda liczba całkowita uwzględnia jedną z liczb0..8
i wskazuje, w których kolumnach można ją znaleźć. zatemb[x][7]==100100001
wskazuje, że do sporządzania roztworu x numer 7 znajduje się w kolumnach 0,5 i 8 (od prawej do lewej). Powodem tej reprezentacji jest to, że trzeba wygenerować resztę możliwości zespołu przez zmiany etykiet, a to reprezentacja sprawia, że jest to wygodne.Dwa powyższe kroki obejmują konfigurację i zajmują około minuty (prawdopodobnie mniej, jeśli usunę niepotrzebne dane wyjściowe. Dwa poniższe kroki to wyszukiwanie).
3 Wyszukuj losowo rozwiązania dla pierwszych dwóch pasm, które się nie kolidują (tzn. Nie mają tej samej liczby dwa razy w danej kolumnie. Wybieramy losowe rozwiązanie dla pasma 1, zakładając zawsze permutację 0, i losowe rozwiązanie dla pasma 2 z losowa permutacja. Wynik zwykle znajduje się w mniej niż 9999 próbach (wskaźnik trafień w pierwszym etapie w zakresie tysięcy) i zajmuje ułamek sekundy. Przez permutację rozumiem, że dla drugiego pasma bierzemy rozwiązanie z b [] [] gdzie pierwszy wiersz to zawsze 012 345 678 i należy go ponownie oznakować, aby możliwa była dowolna sekwencja liczb w pierwszym rzędzie.
4 Po znalezieniu trafienia w kroku 3 wyszukaj rozwiązanie dla trzeciego pasma, które nie koliduje z pozostałymi dwoma pasmami. Nie chcemy podejmować tylko jednej próby, w przeciwnym razie czas przetwarzania dla kroku 3 zostałby zmarnowany. Z drugiej strony nie chcemy wkładać w to nadmiernej ilości wysiłku.
Dla zabawy, ostatniej nocy zrobiłem to najgłupszy możliwy sposób, ale wciąż było to interesujące (ponieważ nic nie było przez wieki, a potem znalazłem dużą liczbę rozwiązań w seriach.) Całą noc zajęło uzyskanie jednego punktu danych, nawet przy małym włamaniu
(!z)
Zrobiłem przerwanie ostatniejk
pętli, gdy tylko wiemy, że nie jest to prawidłowe rozwiązanie (co sprawia, że działa prawie 9 razy szybciej). Znaleziono 1186585 rozwiązań dla pełnej siatki po przeszukaniu wszystkich 362880 ponownych oznakowań wszystkich 1306368 rozwiązań kanonicznych dla ostatniego blok, w sumie 474054819840 możliwości. To wskaźnik trafień 1 na 400000 dla drugiego etapu. Spróbuję wkrótce ponownie z losowym wyszukiwaniem zamiast skanem. Powinien dać rozsądną odpowiedź w ciągu zaledwie kilku milionów prób, co powinno zająć tylko kilka sekund.Ogólna odpowiedź powinna wynosić (362880 * (1306368 * 2)) ^ 3 * wskaźnik trafień = 8,5E35 * wskaźnik trafień. Obliczając wstecz z liczby w pytaniu, oczekuję wskaźnika trafień 1 / 1.2E14. To, co do tej pory mam z moim pojedynczym punktem danych, to 1 / (400000 * 1000), który jest około milion razy większy. Może to być anomalia przypadku, błąd w moim programie lub błąd w matematyce. Nie będę wiedział, co to jest, dopóki nie przeprowadzę jeszcze kilku testów.
Zostawię to tutaj na wieczór. Tekst jest nieco skąpy, wkrótce go uporządkuję i mam nadzieję, że dodam więcej wyników, a może kilka słów o tym, jak przyspieszyć i rozszerzyć koncepcję do N = 4. Jednak nie sądzę, że wprowadzę zbyt wiele zmian w moim programie :-)
Ach .. program:
źródło