CJam, 189 187 байт
Это будет сложно объяснить ... Сложность времени гарантированно будет 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=},,}{!!}?
Если вы достаточно смелы, попробуйте это онлайн . На моем дерьмовом ноутбуке я могу получить до 6 с переводчиком Java или 5 с онлайн-переводчиком.
объяснение
У меня нет большого образования по математике (только что закончил среднюю школу, на следующей неделе я начал учиться в университете). Поэтому терпите меня, если я совершаю ошибки, излагаю очевидное или совершаю ужасно неэффективные действия.
Мой подход - грубая сила, хотя я пытался сделать это немного умнее. Основные шаги:
- Генерация всех возможных операндов ∗ для группы порядка n (т. Е. Перечисление всех групп порядка n );
- Генерация всех возможных биекций φ между двумя группами порядка n ;
- Используя результаты шагов 1 и 2, определите все изоморфизмы между двумя группами порядка n ;
- Используя результат шага 3, посчитайте количество групп с точностью до изоморфизма.
Прежде чем посмотреть, как выполняется каждый шаг, давайте разберемся с некоторым тривиальным кодом:
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)
Следующий алгоритм не работает правильно при n <4 , случаи от 0 до 3 обрабатываются с двойным отрицанием.
Отныне элементы группы будут записываться как {1, a, b, c, ...} , где 1 - элемент идентичности. В реализации CJam соответствующими элементами являются {0, 1, 2, 3, ...} , где 0 - это элемент идентичности.
Начнем с шага 1. Запись всех возможных операторов для группы порядка n эквивалентна генерации всех действительных n × n таблиц Кэли . Первая строка и столбец тривиальны: они оба {1, a, b, c, ...} (слева направо, сверху вниз).
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
Знание того, что таблица Кэли также является уменьшенным латинским квадратом (из-за свойства отмены), позволяет генерировать возможные таблицы построчно. Начиная со второй строки (индекс 1), мы генерируем все уникальные перестановки для этой строки, сохраняя в первом столбце значение индекса.
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
Конечно, не все эти перестановки верны: каждая строка и столбец должны содержать все элементы ровно один раз. Блок фильтра используется для этой цели (истинное значение сохраняет перестановку, ложное удаляет ее):
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
Обратите внимание, что я фильтрую внутри цикла генерации: это делает код немного длиннее (по сравнению с отдельной генерацией и фильтрацией), но значительно повышает производительность. Количество перестановок множества размера n равно n! поэтому более короткое решение потребует много памяти и времени.
Список допустимых таблиц Кейли - отличный шаг к перечислению операторов, но, будучи двумерной структурой, он не может проверить ассоциативность, которая является трехмерным свойством. Поэтому следующим шагом является фильтрация неассоциативных функций.
{ }, 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]
Уф! Много работы, но теперь мы перечислили все группы порядка n (или лучше, операции над ним - но набор фиксирован, так что это одно и то же). Следующий шаг: найти изоморфизмы. Изоморфизм - это биекция между двумя такими группами, что φ (x ∗ y) = φ (x) ∗ φ (y) . Генерация этих биекций в CJam тривиальна: Ne!
сделаем это. Как мы можем их проверить? Мое решение начинается с двух копий таблицы Кэли для x ∗ y . На одном экземпляре φ применяется ко всем элементам, не затрагивая порядок строк или столбцов. Это порождает таблицу для φ (x ∗ y) . С другой стороны, элементы остаются без изменений, но строки и столбцы отображаются через φ . То есть строка / столбецх становится строкой / столбцом ф (х) . Это порождает таблицу для φ (x) ∗ φ (y) . Теперь, когда у нас есть две таблицы, мы просто должны сравнить их: если они одинаковы, мы нашли изоморфизм.
Конечно, нам также нужно сгенерировать пары групп для проверки изоморфизма. Нам нужны все 2-комбинации групп. Похоже, у CJam нет оператора для комбинаций. Мы можем генерировать их, беря каждую группу и комбинируя ее только с элементами, следующими за ней в списке. Интересный факт: количество 2-комбинаций равно n × (n - 1) / 2 , что также является суммой первых n - 1 натуральных чисел. Такие числа называются треугольными числами: попробуйте алгоритм на бумаге, по одной строке на каждый фиксированный элемент, и вы поймете, почему.
: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
Приведенный выше код фиксирует первый элемент пары, L [X] , и объединяет его с другими группами (назовем каждую из этих Y ). Он передает пару в тестовый блок изоморфизма, который я покажу позже. Блок возвращает список значений Y , для которых L [X] изоморфна Y . Затем L [X] добавляется в этот список. Прежде чем понять, почему списки настроены таким образом, давайте посмотрим на тест на изоморфизм:
\_@ 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
Отлично, теперь у нас есть список множеств, таких как [{L [0], Y1, Y2, ...}, {L [1], Y1, ...}, ...] . Идея здесь состоит в том, что по транзитивному свойству, если любые два набора имеют хотя бы один общий элемент, то все группы в этих двух наборах изоморфны. Они могут быть объединены в один набор. Поскольку L [X] никогда не появится в комбинациях, сгенерированных L [X + ...] , после агрегирования каждый набор изоморфизмов будет иметь один уникальный элемент. Итак, чтобы получить количество изоморфизмов, достаточно подсчитать, сколько групп появляется ровно один раз во всех наборах изоморфных групп. Для этого я разворачиваю наборы так, чтобы они выглядели как [L [0], Y1, Y2, ..., L [1], Y1, ...] , сортирую список, чтобы создать кластеры из одной группы, и, наконец, 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
Вот и все, ребята.