CJam, 189 187 bajtów
Trudno to wytłumaczyć ... Gwarantowana jest złożoność czasu O(scary)
.
qi:N_3>{,aN*]N({{:L;N,X)-e!{X)_@+L@@t}%{X2+<z{_fe=:(:+}%:+!},}%:+}fX{:G;N3m*{_~{G@==}:F~F\1m>~F\F=}%:*},:L,({LX=LX)>1$f{\_@a\a+Ne!\f{\:M;~{M\f=z}2*\Mff==}:|{;}|}\a+}fX]:~$e`{0=1=},,}{!!}?
Jeśli masz dość odwagi, wypróbuj go online . Na moim kiepskim laptopie mogę uzyskać do 6 przy pomocy interpretera Java lub 5 przy tłumaczu online.
Wyjaśnienie
Nie mam dużego doświadczenia matematycznego (właśnie skończyłem szkołę średnią, zaczynając CS na uniwersytecie w przyszłym tygodniu). Więc miejcie mnie przy sobie, jeśli popełniam błędy, stwierdzam to, co oczywiste lub robię rzeczy w okropnie nieskuteczny sposób.
Moje podejście to brutalna siła, choć starałem się uczynić to trochę bardziej sprytnym. Główne kroki to:
- Wygeneruj wszystkie możliwe operandy ∗ dla grupy rzędu n (tj. Wylicz wszystkie grupy rzędu n );
- Wygeneruj wszystkie możliwe wstrząsy φ między dwiema grupami rzędu n ;
- Wykorzystując wyniki z kroków 1 i 2, określ wszystkie izomorfizmy między dwiema grupami rzędu n ;
- Wykorzystując wynik z kroku 3, policz liczbę grup aż do izomorfizmu.
Zanim spojrzymy na to, jak wykonuje się każdy krok, usuńmy z tego jakiś trywialny kod:
qi:N_ e# Get input as integer, store in N, make a copy
3>{...} ? e# If N > 3, do... (see below)
{!!} e# Else, push !!N (0 if N=0, 1 otherwise)
Poniższy algorytm nie działa poprawnie dla n <4 , przypadki od 0 do 3 są obsługiwane z podwójną negacją.
Odtąd elementy grupy będą zapisywane jako {1, a, b, c, ...} , gdzie 1 to element tożsamości. W implementacji CJam odpowiednimi elementami są {0, 1, 2, 3, ...} , gdzie 0 to element tożsamości.
Zacznijmy od kroku 1. Zapisanie wszystkich możliwych operatorów dla grupy rzędu n jest równoważne wygenerowaniu wszystkich prawidłowych tabel Cayley n × n . Pierwszy wiersz i kolumna są trywialne: oba są {1, a, b, c, ...} (od lewej do prawej, od góry do dołu).
e# N is on the stack (duplicated before the if)
,a e# Generate first row [0 1 2 3 ...] and wrap it in a list
N* e# Repeat row N times (placeholders for next rows)
] e# Wrap everything in a list
e# First column will be taken care of later
Wiedza, że tabela Cayleya jest również zmniejszonym kwadratem łacińskim (ze względu na właściwość anulowania) pozwala generować możliwe tabele wiersz po rzędzie. Zaczynając od drugiego wiersza (indeks 1), generujemy wszystkie unikalne permutacje dla tego wiersza, utrzymując pierwszą kolumnę ustawioną na wartość indeksu.
N({ }fX e# For X in [0 ... N-2]:
{ }% e# For each table in the list:
:L; e# Assign the table to L and pop it off the stack
N, e# Push [0 ... N-1]
X) e# Push X+1
- e# Remove X+1 from [0 ... N-1]
e! e# Generate all the unique permutations of this list
{ }% e# For each permutation:
X)_ e# Push two copies of X+1
@+ e# Prepend X+1 to the permutation
L@@t e# Store the permutation at index X+1 in L
{...}, e# Filter permutations (see below)
:+ e# Concatenate the generated tables to the table list
Oczywiście nie wszystkie te permutacje są ważne: każdy wiersz i kolumna musi zawierać wszystkie elementy dokładnie jeden raz. W tym celu stosuje się blok filtra (prawdziwa wartość utrzymuje permutację, a fałsz ją usuwa):
X2+ e# Push X+2
< e# Slice the permutations to the first X+2 rows
z e# Transpose rows and columns
{ }% e# For each column:
_fe= e# Count occurences of each element
:( e# Subtract 1 from counts
:+ e# Sum counts together
:+ e# Sum counts from all columns together
! e# Negate count sum:
e# if the sum is 0 (no duplicates) the permutation is kept
e# if the sum is not zero the permutation is filtered away
Zauważ, że filtruję wewnątrz pętli generacyjnej: powoduje to, że kod jest nieco dłuższy (w porównaniu do odrębnego generowania i filtrowania), ale znacznie poprawia wydajność. Liczba permutacji zestawu wielkości n wynosi n! , więc krótsze rozwiązanie wymagałoby dużo pamięci i czasu.
Lista prawidłowych tabel Cayleya jest wielkim krokiem w kierunku wyliczenia operatorów, ale ponieważ jest strukturą 2D, nie może sprawdzić powiązania, które jest właściwością 3D. Kolejnym krokiem jest odfiltrowanie funkcji niepowiązanych.
{ }, e# For each table, keep table if result is true:
:G; e# Store table in G, pop it off the stack
N3m* e# Generate triples [0 ... N-1]^3
{ }% e# For each triple [a b c]:
_~ e# Make a copy, unwrap top one
{ }:F e# Define function F(x,y):
G@== e# x∗y (using table G)
~F e# Push a∗(b∗c)
\1m> e# Rotate triple right by 1
~ e# Unwrap rotated triple
F\F e# Push (a∗b)∗c
= e# Push 1 if a∗(b∗c) == (a∗b)∗c (associative), 0 otherwise
:* e# Multiply all the results together
e# 1 (true) only if F was associative for every [a b c]
Uff! Dużo pracy, ale teraz wyliczyliśmy wszystkie grupy rzędu n (lub lepiej, operacje na nim - ale zestaw jest ustalony, więc to samo). Następny krok: znajdź izomorfizmy. Izomorfizm to zderzenie między dwiema tymi grupami, tak że φ (x ∗ y) = φ (x) ∗ φ (y) . Generowanie tych bijectów w CJam jest banalne: Ne!
zrobi to. Jak możemy to sprawdzić? Moje rozwiązanie zaczyna się od dwóch kopii tabeli Cayleya dla x ∗ y . Na jednym egzemplarzu φ jest stosowany do wszystkich elementów, bez zmiany kolejności wierszy lub kolumn. To generuje tabelę dla φ (x ∗ y) . Z drugiej strony elementy pozostały takie, jakie są, ale wiersze i kolumny są odwzorowane przez φ . To znaczy wiersz / kolumnax staje się wierszem / kolumną φ (x) . To generuje tabelę dla φ (x) ∗ φ (y) . Teraz, gdy mamy dwie tabele, musimy je po prostu porównać: jeśli są takie same, znaleźliśmy izomorfizm.
Oczywiście musimy również wygenerować pary grup do przetestowania izomorfizmu. Potrzebujemy wszystkich 2 kombinacji grup. Wygląda na to, że CJam nie ma operatora dla kombinacji. Możemy je wygenerować, biorąc każdą grupę i łącząc ją tylko z następującymi po niej elementami na liście. Ciekawostka: liczba 2 kombinacji to n × (n - 1) / 2 , co jest również sumą pierwszych n - 1 liczb naturalnych. Takie liczby nazywane są liczbami trójkątnymi: wypróbuj algorytm na papierze, jeden rząd na stały element, a zobaczysz dlaczego.
:L e# List of groups is on stack, store in L
,( e# Push len(L)-1
{ }fX e# For X in [0 ... len(L)-2]:
LX= e# Push the group L[X]
LX)> e# Push a slice of L excluding the first X+1 elements
1$ e# Push a copy of L[X]
f{...} e# Pass each [L[X] Y] combination to ... (see below)
e# The block will give back a list of Y for isomorphic groups
\a+ e# Append L[X] to the isomorphic groups
] e# Wrap everything in a list
Powyższy kod naprawia pierwszy element pary, L [X] , i łączy go z innymi grupami (nazwijmy każdy z tych Y ). Przekazuje parę do bloku testowego izomorfizmu, który zaraz pokażę. Blok oddaje listę wartości Y dla których L [X] jest izomorficzny Y . Następnie L [X] jest dołączany do tej listy. Zanim zrozumiemy, dlaczego listy są ustawione w taki sposób, spójrzmy na test izomorficzny:
\_@ e# Push a copy of Y
a\a+ e# L[X] Y -> [L[X] Y]
Ne! e# Generate all bijective mappings
\f{ } e# For each bijection ([L[X] Y] extra parameter):
\:M; e# Store the mapping in M, pop it off the stack
~ e# [L[X] Y] -> L[X] Y
{ }2* e# Repeat two times (on Y):
M\f= e# Map rows (or transposed columns)
z e# Transpose rows and columns
e# This generates φ(x) ∗ φ(y)
\Mff= e# Map elements of L[X], generates φ(x ∗ y)
= e# Push 1 if the tables are equal, 0 otherwise
:| e# Push 1 if at least a mapping was isomorphic, 0 otherwise
{;}| e# If no mapping was isomorphic, pop the copy of Y off the stack
Świetnie, teraz mamy listę zestawów takich jak [{L [0], Y1, Y2, ...}, {L [1], Y1, ...}, ...] . Chodzi o to, że według własności przechodnich, jeśli dowolne dwa zestawy mają co najmniej jeden wspólny element, wówczas wszystkie grupy w dwóch zestawach są izomorficzne. Można je agregować w jeden zestaw. Ponieważ L [X] nigdy nie pojawi się w kombinacjach generowanych przez L [X + ...] , po agregacji każdy zestaw izomorfizmów będzie miał jeden unikalny element. Aby więc uzyskać liczbę izomorfizmów, wystarczy policzyć, ile grup pojawia się dokładnie raz we wszystkich zestawach grup izomorficznych. Aby to zrobić, rozpakowuję zestawy, aby wyglądały jak [L [0], Y1, Y2, ..., L [1], Y1, ...] , posortuj listę, aby utworzyć klastry tej samej grupy i na końcu Kodowanie RLE.
:~ e# Unwrap sets of isomorphic groups
$ e# Sort list
e` e# RLE-encode list
{ }, e# Filter RLE elements:
0= e# Get number of occurrences
1= e# Keep element if occurrences == 1
, e# Push length of filtered list
e# This is the number of groups up to isomorphism
To wszystko, ludzie.