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=},,}{!!}?
如果您足够勇敢,请在线尝试。在笨拙的笔记本电脑上,使用Java解释器最多可以得到6个,而在在线解释器中最多可以得到5个。
说明
我没有什么数学背景(刚读完高中,下周从大学开始CS)。因此,如果我犯了错误,说出明显的话或以可怕而无效的方式做事,请忍受我。
我的方法是蛮力的,尽管我试图使其更加聪明。主要步骤是:
- 为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 Cayley表。第一行和第一列都很简单:它们都是{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
知道Cayley表也是缩小的拉丁方(由于cancel属性),可以逐行生成可能的表。从第二行(索引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!,因此较短的解决方案将需要大量的内存和时间。
有效的Cayley表列表是枚举运算符的重要步骤,但由于是2D结构,因此无法检查关联性(这是3D属性)。因此,下一步是过滤掉非关联函数。
{ }, 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阶的所有组(或者更好的是,对n的运算-但是集合是固定的,因此是同一件事)。下一步:找到同构。同构是这两个组之间的双射,使得φ(x ∗ y)=φ(x)∗φ(y)。在CJam中生成这些双射是微不足道的:Ne!
将做到这一点。我们如何检查它们?我的解决方案从凯莱表的两个副本开始X * Y。在一个副本上,将φ应用于所有元素,而不会触及行或列的顺序。这将生成φ(x ∗ y)的表。另一方面,元素保持原样,但行和列通过φ映射。也就是说,行/列x成为行/列φ(x)。这将生成φ(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
就是这样,伙计们。