Винтик и Шпунтик, часть 3: лемма Бернсайда и генерация орбит
Это третья часть моих наработок по решению задачи Винтика и Шпунтика в рамках челленджа @vvvphoenix. В прошлой части мы хорошо так свернули формулу включений-исключений для ускорения вычисления ответа. В этой части мы дополнительно ускорим вычисление формулы, разбив слагаемые формулы на классы эквивалентности, где в каждом классе слагаемые одинаковые и их надо будет вычислять только один раз. В этом нам поможет комбинаторная теория групп и её применение в задачах о раскрасках. По большей части эта статья содержит общую теорию решения подобных задач, так что эта информация может быть полезна и вне контекста задачи про Винтика и Шпунтика.
Итак, ссылки на часть 1 и часть 2. Впрочем, для тех, кто в предыдущие части не хочет сильно вчитываться — мы свели решение задачи с параметрами
где
Функция
Таким образом, если мы объединим все шляпы в так называемые классы эквивалентности, где в каждом классе будут находиться одинаковые шляпы с точностью до перестановки слоёв, мы сможем вычислить ответ быстрее. Забегая вперёд — эти классы эквивалентности на языке комбинаторной теории групп называются орбитами. Тогда формула примет вид
где
Сначала я изложу базовые сведения из теории групп, которые, в принципе, можно найти в любом учебнике на соответствующую тему. Но, возможно, моё изложение для кого-нибудь будет доходчивее того, что можно найти в учебнике. Кроме того, эти сведения понадобятся при описании алгоритмов, которые вроде бы достаточно известны, но не встречались мне в учебниках (может быть я плохо искал).
Азы теории групп
Множество
ассоциативность:
для любых , наличие нейтрального элемента
, такого, что для любого , наличие обратного элемента
для любого , при этом .
Далее для операции
Для произвольного подмножества элементов
Лемма 1. Для любого
Доказательство. Каждому элементу
Смысл Леммы 1 в том, что мы можем прокручивать произвольные подмножества
Подгруппой
Левым смежным классом группы
Лемма 2. Смежные классы
Доказательство. Достаточно показать, что если у двух смежных классов есть общий элемент, то эти смежные классы совпадают. Пусть для смежных классов
Для подгруппы
Теорема Лагранжа. В конечных группах порядок любой подгруппы делит порядок группы.
Действие группы на множестве
Группы и их элементы зачастую довольно абстрактны в том смысле, что это — какие-то абстрактные действия в "вакууме". Чтобы получить наглядные примеры и больше пользы от происходящего — эти действия нужно к чему-то применять, к каким-то объектам.
Обозначим множество объектов, на которые мы будем применять нашу группу
ассоциативность: для любых
и любого верно , нейтральный элемент для любого
ничего не меняет: .
Множества
Орбитой элемента
Лемма 3. Две орбиты либо совпадают, либо не пересекаются.
Доказательство. Аналогично тому, что мы делали со смежными классами: пусть элемент
Также заметим, что любой элемент
Пример 1. В качестве
За
Пример 2. Пусть
Пример 3.шляпы чайники). Ну и для двух элементов из
Пример 4.
Пример 5. Предыдущие примеры были не совсем корректные в рамках данной статьи в том плане, что
Пример 6 совсем игрушечный, с которым мы будем работать дальше. Пусть
Нейтральный элемент
Обмен строк
Обмен столбцов
Композиция элементов 2 и 3, когда мы обмениваем ячейки таблицы крест-накрест
Результат действия каждого элемента группы
Аккуратно проанализировав все 16 раскрасок, можно разбить их все на следующие 7 орбит:
Лемма Бернсайда и теорема Пойа
Стабилизатором элемента
Множеством неподвижных точек для элемента
В нашем игрушечном примере в каждой строке таблицы красными кружочками я отметил множества неподвижных точек для каждого элемента группы, а в каждом столбце — элементы множества
Лемма 4. Имеет место тождество
Доказательство. Левая часть равенства — это красные кружочки, просуммированные по строкам, а правая часть — они же, просуммированные по столбцам.
Лемма 5. В рамках действия группы
Доказательство. Сначала заметим, что
где
Теперь докажем, что для любых
Осталось осознать, что
Лемма Бернсайда. Пусть
Доказательство. В одну строчку:
Теперь пройдёмся по формуле. Сначала бы проходимся по всем орбитам и всем элементам в орбите назначаем вес
Зачем вообще нужна лемма Бернсайда? С её помощью мы можем быстрее посчитать количество орбит когда порядок группы
Если
А перед тем как мы к ней перейдем, отмечу, что дальше до конца статьи речь будет идти только про такие
Теорема Пойа. Пусть
Доказательство. Нужно показать, что
Для примера применим Теорему Пойа к нашему игрушечному примеру. У нас в
Сошлось!
Перед тем, как это всё расписывать, я думал, что писанины будет раза в 2 меньше. Впрочем, теперь я могу вывести и доказать лемму Бернсайда с нуля, даже если меня разбудить посреди ночи...
Количество орбит шляп
Теперь можно применить теорему Пойа к нашей задачи имени Винтика и Шпунтика. У нас объекты в
import itertools
import math
def get_num_of_classes( N, M, K ):
Gsz = math.factorial(N) * math.factorial(M) * math.factorial(K)
sum = 0
for pi in itertools.permutations(range(N)):
for pj in itertools.permutations(range(M)):
for pk in itertools.permutations(range(K)):
F = [False] * (N*M*K)
cycles = 0
for i in range(N):
for j in range(M):
for k in range(K):
if F[i*M*K+j*K+k]: continue
cycles += 1
ii,jj,kk = i,j,k
while not F[ii*M*K+jj*K+kk]:
F[ii*M*K+jj*K+kk] = True
ii,jj,kk = pi[ii],pj[jj],pk[kk]
sum += 2 ** cycles
print( (N,M,K), 2 ** (N*M*K), sum // Gsz )
return sum // Gsz
Запустим код и получим следующее для наших 14 задач:
Задача | Количество шляп | Количество орбит | Сложн. до | Сложн. после |
(2, 3, 7, 42) | 4398046511104 | 100141043 | 48 | 32.57 |
(2, 3, 8, 24) | 281474976710656 | 887548063 | 54 | 35.72 |
(2, 3, 9, 18) | 18014398509481984 | 7096254602 | 60 | 38.72 |
(2, 3, 10, 15) | 1152921504606846976 | 51789240476 | 66 | 41.59 |
(2, 3, 12, 12) | 4722366482869645213696 | 78 | 45.86 | |
(2, 4, 5, 20) | 1099511627776 | 200402630 | 48 | 35.57 |
(2, 4, 6, 12) | 281474976710656 | 8658343372 | 56 | 41.01 |
(2, 4, 8, 8) | 18446744073709551616 | 10625165137006 | 72 | 51.27 |
(2, 5, 5, 10) | 1125899906842624 | 39923986958 | 60 | 45.21 |
(2, 6, 6, 6) | 4722366482869645213696 | 4589162146117352 | 84 | 64.02 |
(3, 3, 4, 12) | 68719476736 | 80733858 | 45 | 35.26 |
(3, 3, 6, 6) | 18014398509481984 | 715653569064 | 63 | 48.38 |
(3, 4, 4, 6) | 281474976710656 | 81704808465 | 60 | 48.24 |
(4, 4, 4, 4) | 18446744073709551616 | 1334776269568928 | 80 | 66.24 |
Сложность решения задачи до — это значение
В задаче
И вот, например, для задачи
Это все, конечно, хорошо, но на самом деле знание о количестве орбит не сильно помогает решить нам нашу задачу. Нам нужны сами орбиты.
Генерация орбит
Как генерировать орбиты? Один из самых простых способов следующий. Можно взять все элементы
Другая идея состоит в том, чтобы выделить из каждой орбиты ровно одного представителя. Обычно это делается так: если объекты из
Как проверить, что объект максимальный? Нужно попробовать применить к нему все действия из группы. Если после какого-то действия в результате получится объект больше — то объект не максимальный. Если увеличения объекта не произошло для всех действий группы — он максимальный.
Посчитать количество объектов в орбите без затрат памяти тоже на самом деле не сложно. Нужно воспользоваться Леммой 5 и посчитать
Таким же способом можно просто искать максимальный объект в орбите. Возьмём какой-то объект, применим к нему действия группы всеми способами и возьмём максимум из того, что получилось. Например, если группа — это множество всех перестановок
Итого все орбиты (как множество представителей) и их длины можно найти без затрат памяти за
Будем генерировать объекты из
Будем называть частично покрашенный объект максимальным, если максимальным является полностью покрашенный объект, который мы можем получить из частично покрашенного путём покраски не покрашенных "штучек" в минимальный цвет из
Лемма 6. Если частично покрашенный объект не является максимальным, то не являются максимальными также все объекты в его поддереве.
Доказательство. Пусть в частично покрашенном объекте
Предположим, что
Значит,
Согласно Лемме 6, если в результате рекурсивного обхода дерева мы наткнёмся на не максимальную вершину, то нам следует откатиться, ибо во всём поддереве нам ловить нечего. Напротив, если частично покрашенный объект максимален — в его поддереве существует максимальный лист. Например, объект, в котором все не прокрашенные "штучки" покрашены в минимальный цвет.
Из предыдущего абзаца следует, что в дереве не больше
Алгоритм можно дополнительно ускорить, используя секретную технику шаолиньских монахов, которую я называю халтурная проверка максимальности. Будем тестировать объекты на максимальность не всеми действиями группы, а только некоторыми, выбранными наугад. Тогда мы сэкономим время на проверку объектов, которые действительно являются максимальными (напомню, что на них надо испробовать все действия группы, чтобы убедиться в максимальности). Не максимальные объекты будут отсеиваться лишь с некоторой вероятностью, и мы будем вынуждены проверять некоторые дополнительные "лишние" не максимальные объекты. Но принцип здесь такой: пусть мы будем проверять в 10 раз больше объектов, зато каждая проверка будет в 100 раз быстрее, тогда в итоге получится ускорение в 10 раз. Понятно, что листы дерева поиска надо будет проверять полностью без всякой халтуры.
Работу алгоритма можно проиллюстрировать следующей картинкой для нашего игрушечного примера:
Как можно видеть, в этом дереве 7 листов, что совпадает с числом орбит.
Эксплуатируем структуру группы
Как было отмечено в предыдущем параграфе, иногда проверять максимальность объекта можно быстрее, чем за
Чуть выше уже упоминалось, что
Вот именно на последней перестановке мы и сэкономим! А сэкономим как и описано выше — просто отсортируем уже перемешанные двумя первыми перестановками слои в порядке убывания. Порядок битов для лексикографического сравнения должен быть соответствующий: из более раннего по порядку слоя биты должны быть более значимы, чем из более позднего. Также биты должны быть одинакового упорядочены по значимости в каждом слое.
Итого получаем сложность
def is_maximal( N, M, K, hat ):
for pi in itertools.permutations(range(N)):
for pj in itertools.permutations(range(M)):
hat2 = [0] * K
for k in range(K):
for i in pi:
for j in pj:
hat2[k] = hat2[k] * 2 + ((hat[k]>>(i*M+j))&1)
hat2.sort( reverse=True )
if hat2 > hat: return False
return True
Тут hat
— это список из
Подсчёт порядка стабилизатора тоже можно ускорить до
def get_size_of_class( N, M, K, hat ):
G_sz = math.factorial(N) * math.factorial(M) * math.factorial(K)
Stab_sz = 0
for pi in itertools.permutations(range(N)):
for pj in itertools.permutations(range(M)):
hat2 = [0] * K
for k in range(K):
for i in pi:
for j in pj:
hat2[k] = hat2[k] * 2 + ((hat[k]>>(i*M+j))&1)
hat2.sort( reverse=True )
if hat == hat2:
ways = 1
cnt = 1
for i in range(1,K+1):
if i==K or hat[i]!=hat[i-1]:
ways *= math.factorial(cnt)
cnt = 1
else: cnt += 1
Stab_sz += ways
return G_sz // Stab_sz
Хм... вычисление ways
можно было бы вынести наружу из цикла, но тут рядом сортировка (а ещё сборка новой шляпы за
Остаток кода и результаты запуска
Собственно, код ниже. Недостающие функции можно выковырять из текста статьи выше.
import sys
import itertools
import math
from timeit import default_timer as timer
def sign( x, L ):
if (L-x.bit_count())&1:
return -1
return 1
def calc_dp2t( M, n, m ):
trits = []
for i in range(n*m):
trits.append( M%3 )
M //= 3
res = 0
for x in range(1<<n):
for y in range(1<<m):
prod = 1
for i in range(n):
for j in range(m):
prod *= trits[i*m+j] + ((x>>i)&1) + ((y>>j)&1)
res += sign(x, n) * sign(y, m) * prod
return res
to_ternary = []
dp = []
max_mask = []
found_classes = 0
num_of_classes = 0
ans = 0
def init( N, M, K ):
global to_ternary, dp, max_mask, found_classes, num_of_classes, ans
# init to_ternary
for i in range(1<<(N*M)):
tmp = 0
for j in range(N*M):
if ((i>>j)&1):
tmp += 3 ** j
to_ternary.append( tmp )
# init dp
dp = [ calc_dp2t( i, N, M ) for i in range(3**(N*M)) ]
# init max_mask
for mask in range(1<<(N*M)):
mx = mask
for pi in itertools.permutations(range(N)):
for pj in itertools.permutations(range(M)):
mask2 = 0
for i in pi:
for j in pj:
mask2 = mask2 * 2 + ((mask>>(i*M+j))&1)
mx = max( mx, mask2 )
max_mask.append( mx )
# init num_of_classes
found_classes = 0
num_of_classes = get_num_of_classes( N, M, K )
ans = 0
def calcF( N, M, K, T, hat ): # hat is list of K masks
global to_ternary, dp
sum = 0
for apron in range(1<<(N*M)):
prod = 1
for part in range(K):
prod *= dp[to_ternary[hat[part]]+to_ternary[apron]]
sum += sign(apron, N*M) * prod
res = sum ** T
for i in range(K): res *= sign( hat[i], N*M )
return res
def dfs( N, M, K, T, i, hat ):
global max_mask, found_classes, num_of_classes, ans
if i==K:
found_classes += 1
if found_classes % 10000 == 0:
print( found_classes, "/", num_of_classes, ans, file=sys.stderr )
ans += calcF( N, M, K, T, hat ) * get_size_of_class( N, M, K, hat )
return
for part in range(1<<(N*M)):
if i>0:
if part > hat[i-1]: break # line 82
if max_mask[part] > hat[0]: continue # line 83
hat[i] = part
if is_maximal( N, M, K, hat ):
dfs( N, M, K, T, i+1, hat )
hat[i] = 0
def solve4d( N, M, K, T ):
global ans
start = timer()
print( (N, M, K, T) )
init( N, M, K )
dfs( N, M, K, T, 0, [0] * K )
print( ans )
end = timer()
print( "time", end - start )
n, m, k, t = map(int, input().split())
solve4d( n, m, k, t )
Комментарии по коду:
В процедуре init
инициализируется всё что можно — таблица dp
, как в предыдущей части, считается число орбит через теорему Пойа, а самое главное — вычисляется список max_mask
. В нём для каждой возможной маски-слоя хранится максимальная маска, которая может быть получена путём перестановки строк и столбцов. Эту предпросчитанную таблицу вы используем далее для ускорения проверки на максимальность.
Рекурсивная процедура dfs
строит дерево перебора шляп. Для листа дерева (когда шляпа полностью сгенерирована) запускается вычисление функцииcalcF
), а также вычисляется размер класса эквивалентности (длина орбиты) для данной шляпы. Шляпы же в процессе рекурсивного обходя дерева строятся не побитово, а послойно. Также включены две дополнительные проверки, чтобы уменьшить количество запусков функции is_maximal
. Первая из них (строка 82) заключается в том, что маски в слоях шляпы должны идти в порядке невозрастания — иначе мы можем поменять местами слои и получить шляпу больше. Вторая проверка (строка 83) использует посчитанную ранее таблицу max_mask
: если маску в текущем слое можно преобразовать в другую, которая больше маски в самом первом слое шляпы, то можно и всю шляпу "перетасовать" этим же преобразованием, а затем перенести маску текущего слоя в самый первый слой. И тогда получится шляпа больше.
В результате этим кодом удалось посчитать ответ для задачи
Примечание. Уже в процессе написания статьи, я нашел в коде баг, где вычислялся список max_mask
(тут уже исправленная версия). Этот баг не влияет на ответ, но может повлиять на время вычислений (мне было лень перезапускать расчеты). Из-за него по сути отсечение в строке 83 почти не работало, что приводило к "лишним" запускам полной проверки максимальности. Так что, возможно, на самом деле код выше считает ответ несколько быстрее.
Замечательная симметрия задач (2, *, *, *)
В ходе исследования получаемых орбит (в целях уменьшения их числа) был замечен и доказан следующий факт:
Лемма 7. Если в шляпе размера
Доказательство. Вспомним структуру функции
Я только заменил вот эти
где
гдe
то есть значение
Таким образом, мы даже доказали чуть больше: если поменять в шляпе местами два бита в любом столбике высоты 2, то значение
Я на скорую руку переделал код под задачи вида
Код для задач особого вида
import sys
import itertools
import math
from timeit import default_timer as timer
def sign( x, L ):
if (L-x.bit_count())&1:
return -1
return 1
def calc_dp2t( M, n, m ):
trits = []
for i in range(n*m):
trits.append( M%3 )
M //= 3
res = 0
for x in range(1<<n):
for y in range(1<<m):
prod = 1
for i in range(n):
for j in range(m):
prod *= trits[i*m+j] + ((x>>i)&1) + ((y>>j)&1)
res += sign(x, n) * sign(y, m) * prod
return res
def get_num_of_classes( N, M, K ):
Gsz = math.factorial(M) * math.factorial(K)
sum = 0
for pj in itertools.permutations(range(M)):
for pk in itertools.permutations(range(K)):
F = [False] * (M*K)
cycles = 0
for j in range(M):
for k in range(K):
if F[j*K+k]: continue
cycles += 1
jj,kk = j,k
while not F[jj*K+kk]:
F[jj*K+kk] = True
jj,kk = pj[jj],pk[kk]
sum += 3 ** cycles
return sum // Gsz
to_ternary = []
dp = []
max_mask = []
found_classes = 0
num_of_classes = 0
ans = 0
T2 = []
def init( N, M, K ):
global to_ternary, dp, max_mask, found_classes, num_of_classes, ans, T2
# init to_ternary
for i in range(1<<(N*M)):
tmp = 0
for j in range(N*M):
if ((i>>j)&1):
tmp += 3 ** j
to_ternary.append( tmp )
# init dp
dp = [ calc_dp2t( i, N, M ) for i in range(3**(N*M)) ]
# init max_mask
for mask in range(1<<(N*M)):
mx = mask
for pi in itertools.permutations(range(N)):
for pj in itertools.permutations(range(M)):
mask2 = 0
for i in pi:
for j in pj:
mask2 = mask2 * 2 + ((mask>>(i*M+j))&1)
mx = max( mx, mask2 )
max_mask.append( mx )
# init num_of_classes
found_classes = 0
num_of_classes = get_num_of_classes( N, M, K )
ans = 0
# init T2 - layers with right ordered columns
for mask in range(1<<(N*M)):
flag = True
for i in range(M):
if ((mask>>i)&1)==1 and ((mask>>(i+M))&1)==0:
flag = False
if flag: T2.append( mask )
def calcF( N, M, K, T, hat ): # hat is list of C masks
global to_ternary, dp
sum = 0
for apron in range(1<<(N*M)):
prod = 1
for part in range(K):
prod *= dp[to_ternary[hat[part]]+to_ternary[apron]]
sum += sign(apron, N*M) * prod
res = sum ** T
for i in range(K): res *= sign( hat[i], N*M )
return res
def is_maximal( N, M, K, hat ):
for pj in itertools.permutations(range(M)):
hat2 = [0] * K
for k in range(K):
for i in range(N-1,-1,-1):
for j in pj:
hat2[k] = hat2[k] * 2 + ((hat[k]>>(i*M+j))&1)
hat2.sort( reverse=True )
if hat2 > hat: return False
return True
def get_size_of_class( N, M, K, hat ):
G_sz = math.factorial(M) * math.factorial(K)
Stab_sz = 0
for pj in itertools.permutations(range(M)):
hat2 = [0] * K
for k in range(K):
for i in range(N-1,-1,-1):
for j in pj:
hat2[k] = hat2[k] * 2 + ((hat[k]>>(i*M+j))&1)
hat2.sort( reverse=True )
if hat == hat2:
ways = 1
cnt = 1
for i in range(1,K+1):
if i==K or hat[i]!=hat[i-1]:
ways *= math.factorial(cnt)
cnt = 1
else: cnt += 1
Stab_sz += ways
pw = 0
for part in hat:
for i in range(M):
if ((part>>i)&1)==0 and ((part>>(i+M))&1)==1:
pw += 1
return G_sz // Stab_sz * (2 ** pw)
def dfs( N, M, K, T, i, hat ):
global max_mask, found_classes, num_of_classes, ans, T2
if i==K:
found_classes += 1
if found_classes % 10000 == 0:
print( found_classes, "/", num_of_classes, ans, file=sys.stderr )
ans += calcF( N, M, K, T, hat ) * get_size_of_class( N, M, K, hat )
return
for part in T2:
if i>0:
if part > hat[i-1]: break
if max_mask[part] > hat[0]: continue
hat[i] = part
if is_maximal( N, M, K, hat ):
dfs( N, M, K, T, i+1, hat )
hat[i] = 0
def solve4d( N, M, K, T ):
global ans
start = timer()
print( (N, M, K, T) )
init( N, M, K )
dfs( N, M, K, T, 0, [0] * K )
print( ans )
end = timer()
print( "time", end - start )
n, m, k, t = map(int, input().split())
solve4d( n, m, k, t )
Изменения не расписываю, статья и так получается неприлично большой. До сюда все равно мало кто дочитает, а если кто и дочитает — думаю, без проблем сможет разобраться в коде.
К сожалению, для столбиков высоты 3 и выше эта идея не работает. Там получается
и вот эти вот произведения портят всю малину, и неясно что с ними делать.
Сводные таблички
Сначала таблица по количеству орбит, которая частично повторяет таблицу выше.
Задача | Количество шляп | Количество орбит | Орбит троичных шляп |
(2, 3, 7, 42) | 4398046511104 | 100141043 | 725560 |
(2, 3, 8, 24) | 281474976710656 | 887548063 | 3061223 |
(2, 3, 9, 18) | 18014398509481984 | 7096254602 | 11854073 |
(2, 3, 10, 15) | 1152921504606846976 | 51789240476 | 42564874 |
(2, 3, 12, 12) | 4722366482869645213696 | ||
(2, 4, 5, 20) | 1099511627776 | 200402630 | 1438896 |
(2, 4, 6, 12) | 281474976710656 | 8658343372 | 20047856 |
(2, 4, 8, 8) | 18446744073709551616 | 10625165137006 | 2691936030 |
(2, 5, 5, 10) | 1125899906842624 | 39923986958 | 64796982 |
(2, 6, 6, 6) | 4722366482869645213696 | 4589162146117352 | 302752867740 |
(3, 3, 4, 12) | 68719476736 | 80733858 | |
(3, 3, 6, 6) | 18014398509481984 | 715653569064 | |
(3, 4, 4, 6) | 281474976710656 | 81704808465 | |
(4, 4, 4, 4) | 18446744073709551616 | 1334776269568928 |
Для задачи
Теперь таблица с примерными сложностями задач и временем их решения:
Задача | Слж1 | Слж2 | Слж3 | Время1 | Время2 | Время3 |
(2, 3, 7, 42) | 48 | 32.57 | 25.46 | 8 месяцев* | 3.3 часа | 1 минута |
(2, 3, 8, 24) | 54 | 35.72 | 27.54 | 29.5 часов | 5 минут | |
(2, 3, 9, 18) | 60 | 38.72 | 29.49 | 256 часов | 20 минут | |
(2, 3, 10, 15) | 66 | 41.59 | 31.34 | 80 минут | ||
(2, 3, 12, 12) | 78 | 45.86 | 34.89 | 15 часов | ||
(2, 4, 5, 20) | 48 | 35.57 | 28.45 | 17 месяцев* | 9 часов | 3 минуты |
(2, 4, 6, 12) | 56 | 41.01 | 32.25 | 40 минут | ||
(2, 4, 8, 8) | 72 | 51.27 | 39.32 | 110 часов | ||
(2, 5, 5, 10) | 60 | 45.21 | 35.94 | 4.4 часа | ||
(2, 6, 6, 6) | 84 | 64.02 | 50.13 | |||
(3, 3, 4, 12) | 45 | 35.26 | 1 месяц | 3 часа | ||
(3, 3, 6, 6) | 63 | 48.38 | ||||
(3, 4, 4, 6) | 60 | 48.24 | ||||
(4, 4, 4, 4) | 80 | 66.24 | 24 млрд лет* | 2 млн лет* |
Алгоритм с пометкой 1 — это алгоритм из предыдущей части, 2 — из текущей "универсальный", 3 — ускоренный для задач вида
Как можно видеть, удалось решить ещё 5 задач, итого 10 из 14. Решение задачи
В следующей таблице посчитанные ответы на посчитанные задачи. Так как числа слишком большие, я разбил их на блоки по 50 цифр. Раскладывал на множители, как обычно, тут.
Задача | Ответ | Цифр |
(2, 3, 7, 42) | 45547211178376523606515359626169685427015437406338 18234705576791599805222175651649918413771712485516 11942407360769291716227341468096354519187004963793 67600348035629119141189588086855391729487239841351 99549030400000000000000000000000000000000000000000 0000000000 = 2501 × 3103 × 551 × 76 × 113 × 133 × 172 × 192 × 23 × 29 × 31 × 37 × 41 | 260 |
(2, 3, 8, 24) | 12158626964391095601402876510764026760893936102375 20566128947343796803849270846828175640235785714886 26792890695921312297244842858226123380138967040000 000000000000000000000000000000 = 2326 × 366 × 534 × 75 × 112 × 13 × 17 × 19 × 23 × 1050451 × 239661299 | 180 |
(2, 3, 9, 18) | 14659600083462744344107687948098132638851854490175 83487915789246514046708546677858426412195890195415 25551079075385660136017100800000000000000000000000 000000 = 2280 × 356 × 529 × 74 × 11 × 13 × 17 × 1481 × 89551067953241 | 156 |
(2, 3, 10, 15) | 35566187691918270224227521123489566281757071652231 90241783980474522205470248634741885160613023923740 9230079000785715200000000000000000000000000000 = 2254 × 354 × 529 × 74 × 11 × 13 × 17 × 19434054570299099 | 146 |
(2, 3, 12, 12) | 53805634014887880990944917904676649446742717692653 02248064649986993442232858736629215316534644318952 03684510288838656000000000000000000000000 = 2244 × 350 × 524 × 73 × 112 × 13 × 33827 × 24371353277984723 | 141 |
(2, 4, 5, 20) | 56090549803543632753658611845075457575564534394506 25753719212425453537621520163078556224745649495003 9175717220420431894762857577840640000 = 2212 × 360 × 54 × 72 × 11 × 13 × 17 × 19 × 179 × 634703 × 1250884162288099198601324717 | 137 |
(2, 4, 6, 12) | 89396965271074790622796082078568467317179511870974 31365421889030129837166131969462204388544827233402 88000 = 2150 × 325 × 53 × 7 × 11 × 10956871 × 119322992752392797 × 58746315651011560153 | 105 |
(2, 4, 8, 8) | 54412760801952357337278999288470639461605380795865 2922862285917121018444715169215342633287680000 = 2135 × 320 × 54 × 73 × 607 × 27533626972345582286420576144661825341 | 96 |
(2, 5, 5, 10) | 25751380617252272796699701337640873547573107139149 852088285440091020314414320091036205763788800 = 2136 × 320 × 52 × 7 × 11 × 44041822213121488066688532612753659706641 | 95 |
(3, 3, 4, 12) | 16257170556870871850339631093052558972779236942231 34108704431016700065145660728200396800 = 283 × 36 × 52 × 7 × 11 × 467 × 32470531 × 7899339661786140369486826344309653421924810349 | 88 |
Они же текстом
(2, 3, 7, 42)
45547211178376523606515359626169685427015437406338182347055767915998052221756516499184137717124855161194240736076929171622734146809635451918700496379367600348035629119141189588086855391729487239841351995490304000000000000000000000000000000000000000000000000000
(2, 3, 8, 24)
121586269643910956014028765107640267608939361023752056612894734379680384927084682817564023578571488626792890695921312297244842858226123380138967040000000000000000000000000000000000
(2, 3, 9, 18)
146596000834627443441076879480981326388518544901758348791578924651404670854667785842641219589019541525551079075385660136017100800000000000000000000000000000
(2, 3, 10, 15)
35566187691918270224227521123489566281757071652231902417839804745222054702486347418851606130239237409230079000785715200000000000000000000000000000
(2, 3, 12, 12)
538056340148878809909449179046766494467427176926530224806464998699344223285873662921531653464431895203684510288838656000000000000000000000000
(2, 4, 5, 20)
56090549803543632753658611845075457575564534394506257537192124254535376215201630785562247456494950039175717220420431894762857577840640000
(2, 4, 6, 12)
893969652710747906227960820785684673171795118709743136542188903012983716613196946220438854482723340288000
(2, 4, 8, 8)
544127608019523573372789992884706394616053807958652922862285917121018444715169215342633287680000
(2, 5, 5, 10)
25751380617252272796699701337640873547573107139149852088285440091020314414320091036205763788800
(3, 3, 4, 12)
1625717055687087185033963109305255897277923694223134108704431016700065145660728200396800
Порядок чисел, кстати, совпадает с оценками, полученными во второй части. Тем не менее, было бы здорово, если бы кто-нибудь независимо с нуля написал бы программу, аналогичную моей, и верифицировал бы ответы. А то мало ли где я баг мог посадить.
Заключение
В общем, мы решили 10 задачек из 14, но до решения задачи
Возможный другой путь дальнейшего развития событий: поиск дополнительных неочевидных симметрий. При этом не обязательно в виде добавления новых действий в группу, можно уже имеющиеся орбиты каким-то образом склеить в кластеры шляп с одинаковым значением функции
А ещё можно поугадывать формулу. Но тут всё совсем неопределённо.