forked from keon/algorithms
-
Notifications
You must be signed in to change notification settings - Fork 0
/
symmetry_group_cycle_index.py
159 lines (130 loc) · 4.18 KB
/
symmetry_group_cycle_index.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""
The significance of the cycle index (polynomial) of symmetry group
is deeply rooted in counting the number of configurations
of an object excluding those that are symmetric (in terms of permutations).
For example, the following problem can be solved as a direct
application of the cycle index polynomial of the symmetry
group.
Note: I came across this problem as a Google's foo.bar challenge at Level 5
and solved it using a purely Group Theoretic approach. :)
-----
Problem:
Given positive integers
w, h, and s,
compute the number of distinct 2D
grids of dimensions w x h that contain
entries from {0, 1, ..., s-1}.
Note that two grids are defined
to be equivalent if one can be
obtained from the other by
switching rows and columns
some number of times.
-----
Approach:
Compute the cycle index (polynomials)
of S_w, and S_h, i.e. the Symmetry
group on w and h symbols respectively.
Compute the product of the two
cycle indices while combining two
monomials in such a way that
for any pair of cycles c1, and c2
in the elements of S_w X S_h,
the resultant monomial contains
terms of the form:
$$ x_{lcm(|c1|, |c2|)}^{gcd(|c1|, |c2|)} $$
Return the specialization of
the product of cycle indices
at x_i = s (for all the valid i).
-----
Code:
def solve(w, h, s):
s1 = get_cycle_index_sym(w)
s2 = get_cycle_index_sym(h)
result = cycle_product_for_two_polynomials(s1, s2, s)
return str(result)
"""
from fractions import Fraction
from typing import Dict, Union
from polynomial import ( Monomial, Polynomial )
from gcd import lcm
def cycle_product(m1: Monomial, m2: Monomial) -> Monomial:
"""
Given two monomials (from the
cycle index of a symmetry group),
compute the resultant monomial
in the cartesian product
corresponding to their merging.
"""
assert isinstance(m1, Monomial) and isinstance(m2, Monomial)
A = m1.variables
B = m2.variables
result_variables = dict()
for i in A:
for j in B:
k = lcm(i, j)
g = (i * j) // k
if k in result_variables:
result_variables[k] += A[i] * B[j] * g
else:
result_variables[k] = A[i] * B[j] * g
return Monomial(result_variables, Fraction(m1.coeff * m2.coeff, 1))
def cycle_product_for_two_polynomials(p1: Polynomial, p2: Polynomial, q: Union[float, int, Fraction]) -> Union[float, int, Fraction]:
"""
Compute the product of
given cycle indices p1,
and p2 and evaluate it at q.
"""
ans = Fraction(0, 1)
for m1 in p1.monomials:
for m2 in p2.monomials:
ans += cycle_product(m1, m2).substitute(q)
return ans
def cycle_index_sym_helper(n: int, memo: Dict[int, Polynomial]) -> Polynomial:
"""
A helper for the dp-style evaluation
of the cycle index.
The recurrence is given in:
https://en.wikipedia.org/wiki/Cycle_index#Symmetric_group_Sn
"""
if n in memo:
return memo[n]
ans = Polynomial([Monomial({}, Fraction(0, 1))])
for t in range(1, n+1):
ans = ans.__add__(Polynomial([Monomial({t: 1}, Fraction(1, 1))]) * cycle_index_sym_helper(n-t, memo))
ans *= Fraction(1, n)
memo[n] = ans
return memo[n]
def get_cycle_index_sym(n: int) -> Polynomial:
"""
Compute the cycle index
of S_n, i.e. the symmetry
group of n symbols.
"""
if n < 0:
raise ValueError('n should be a non-negative integer.')
memo = {
0: Polynomial([
Monomial({}, Fraction(1, 1))
]),
1: Polynomial([
Monomial({1: 1}, Fraction(1, 1))
]),
2: Polynomial([
Monomial({1: 2}, Fraction(1, 2)),
Monomial({2: 1}, Fraction(1, 2))
]),
3: Polynomial([
Monomial({1: 3}, Fraction(1, 6)),
Monomial({1: 1, 2: 1}, Fraction(1, 2)),
Monomial({3: 1}, Fraction(1, 3))
]),
4: Polynomial([
Monomial({1: 4}, Fraction(1, 24)),
Monomial({2: 1, 1: 2}, Fraction(1, 4)),
Monomial({3: 1, 1: 1}, Fraction(1, 3)),
Monomial({2: 2}, Fraction(1, 8)),
Monomial({4: 1}, Fraction(1, 4)),
])
}
result = cycle_index_sym_helper(n, memo)
return result