Как стать автором
Поиск
Написать публикацию
Обновить

Винтик и Шпунтик, часть 3: лемма Бернсайда и генерация орбит

Уровень сложностиСредний
Время на прочтение26 мин
Количество просмотров1.5K
Винтик и Шпунтик сравнивают шляпы
Винтик и Шпунтик сравнивают шляпы

Это третья часть моих наработок по решению задачи Винтика и Шпунтика в рамках челленджа @vvvphoenix. В прошлой части мы хорошо так свернули формулу включений-исключений для ускорения вычисления ответа. В этой части мы дополнительно ускорим вычисление формулы, разбив слагаемые формулы на классы эквивалентности, где в каждом классе слагаемые одинаковые и их надо будет вычислять только один раз. В этом нам поможет комбинаторная теория групп и её применение в задачах о раскрасках. По большей части эта статья содержит общую теорию решения подобных задач, так что эта информация может быть полезна и вне контекста задачи про Винтика и Шпунтика.

Итак, ссылки на часть 1 и часть 2. Впрочем, для тех, кто в предыдущие части не хочет сильно вчитываться — мы свели решение задачи с параметрами(n,m,k,t)к вычислению некой формулы вида

\sum_{h \in H} \mathcal{F}(h,t),

гдеH— множество всех параллелепипедов размераn \times m \times k, где в каждой ячейке стоит 0 или 1. Итого2^{nmk}слагаемых. Эти параллелепипеды мы назвали шляпами.

Функция\mathcal{F}устроена так, что для двух шляп, отличающихся только перестановкой слоёв, значение\mathcal{F}одинаково. Действительно, если мы переставим какие-то слои в шляпе, то мы можем соответствующим способом переставить слои в фартуках (для тех, кто не читал предыдущие части — эти фартуки внутри\mathcal{F}), после чего только поменяется порядок слагаемых внутри\mathcal{F}, а от перестановки слагаемых, как известно, сумма не меняется.

Таким образом, если мы объединим все шляпы в так называемые классы эквивалентности, где в каждом классе будут находиться одинаковые шляпы с точностью до перестановки слоёв, мы сможем вычислить ответ быстрее. Забегая вперёд — эти классы эквивалентности на языке комбинаторной теории групп называются орбитами. Тогда формула примет вид

\sum_{h \in C} |Orb(h)| \cdot \mathcal{F}(h,t),

гдеC— множество представителей орбит (по одному из каждой орбиты), а|Orb(h)|— длина орбиты, то есть количество шляп в соответствующем классе эквивалентности.

Сначала я изложу базовые сведения из теории групп, которые, в принципе, можно найти в любом учебнике на соответствующую тему. Но, возможно, моё изложение для кого-нибудь будет доходчивее того, что можно найти в учебнике. Кроме того, эти сведения понадобятся при описании алгоритмов, которые вроде бы достаточно известны, но не встречались мне в учебниках (может быть я плохо искал).

Азы теории групп

МножествоGс заданной на нём бинарной операцией*: G \times G \to G(то естьGзамкнуто относительно*: для любыхg_1, g_2 \in Gтакжеg_1 * g_2 = g' \in G) называется группой(G,*), если выполняются следующие аксиомы:

  1. ассоциативность:(a * b) * c = a * (b * c)для любыхa, b, c \in G,

  2. наличие нейтрального элементаe \in G, такого, чтоe * a = a * e = aдля любогоa \in G,

  3. наличие обратного элементаa^{-1} \in Gдля любого a \in G, при этом
    a * a^{-1} = a^{-1} * a = e.

Далее для операции*мы будем использовать символ умножения, то естьa * bпревратится в простоab. Отметим, что в общем случае операция некоммутативна, то есть равенствоab = baв общем случае неверно.

Для произвольного подмножества элементовH \subseteq Gи произвольного элементаa \in Gопределим множествоaHкак\{ ah : h \in H \}. Аналогично можно определитьHa = \{ ha : h \in H \}.

|X|— мощность множестваX, то есть количество элементов вX. Далее мы будем работать только с конечными группами, то есть|X|всегда будет равно какому-то неотрицательному целому числу. ЕслиG— группа, то|G|также называется порядком группы.

Лемма 1. Для любогоH \subseteq Gи любогоa \in Gимеет место|aH| = |H| = |Ha|.

Доказательство. Каждому элементуh \in Hпоставим в соответствие элементah \in aH, а каждому элементу h' = ah \in aH поставим в соответствие элементa^{-1}h' = a^{-1}ah = h \in H. Таким образом, получаем взаимно однозначное соответствие (биекцию) между элементамиHиaH, откуда|aH| = |H|. Аналогично можно доказать, что|Ha| = |H|.\blacksquare

Смысл Леммы 1 в том, что мы можем прокручивать произвольные подмножестваHпо группеG, и в процессе у нас ничего не слипнется. Мы также всегда можем открутитьHобратно.

ПодгруппойHгруппыGназывается подмножествоH \subseteq G, которое само по себе является группой с той же бинарной операцией. Например,\{e\}иG— подгруппы любой группыG.

Левым смежным классом группыGпо подгруппеHназывается множествоaHдля некоторогоa \in G. Аналогично можно определить правый смежный классHa. Далее мы будем работать только с левыми смежными классами и для краткости называть их просто "смежные классы".

Лемма 2. Смежные классыGпо подгруппеHлибо совпадают, либо не пересекаются.

Доказательство. Достаточно показать, что если у двух смежных классов есть общий элемент, то эти смежные классы совпадают. Пусть для смежных классовaHиbHдля некоторыхh_a, h_b \in Hвыполняетсяah_a = bh_b. Немного преобразуем последнее равенство:a = b h_b h_a^{-1}. Тогда для любого элементаah \in aHимеемah = b(h_bh_a^{-1}h). Ноh_bh_a^{-1}h \in H, значитah \in bH. Пройдясь по всемah \in aH, получим, чтоaH \subseteq bH. Аналогично пройдясь по всемbh \in bH, получимbH \subseteq aH. А это значит, чтоaH = bH.\blacksquare

Для подгруппыHгруппы G каждый элементa \in Gпринадлежит ровно одному смежному классу, а именно —a \in aH(двум смежным классамaпринадлежать не может по Лемме 2). Стало быть, все элементыGразбиваются на непересекающиеся смежные классы. По Лемме 1 размер каждого смежного классаaHравен|aH| = |H|. Отсюда прямиком следует

Теорема Лагранжа. В конечных группах порядок любой подгруппы делит порядок группы.

Действие группы на множестве

Группы и их элементы зачастую довольно абстрактны в том смысле, что это — какие-то абстрактные действия в "вакууме". Чтобы получить наглядные примеры и больше пользы от происходящего — эти действия нужно к чему-то применять, к каким-то объектам.

Обозначим множество объектов, на которые мы будем применять нашу группуG (с бинарной операцией*), какX. Мы также должны ввести операцию действия группы (слева!)\oplus : G \times X \to X, которая каждой паре элемента группыGи элемента множестваXбудет ставить в соответствие какой-то элемент изX. Кроме, того, чтобы всё работало, нужно выполнение дополнительных аксиом:

  1. ассоциативность: для любыхa, b \in Gи любогоx \in Xверно(a*b) \oplus x = a *(b \oplus x),

  2. нейтральный элемент для любогоx \in Xничего не меняет:e \oplus x = x.

МножестваGиXв общем случае совершенно различной природы, поэтому операции*и\oplusя обозначил разными символами. Впрочем, можно взятьX = Gи\oplus = *, и получить действие группы на себя, но это уже всё было в предыдущем параграфе. Далее оба символа*и\oplusбудут опускаться, и вместоa * b \oplus xмы будем писать простоabx, где там какие операции ясно из контекста (типов объектов — такой вот полиморфизм).

Орбитой элементаx \in X, на который действует группаG, называется множествоOrb(x) = Gx = \{ gx : g \in G \} \subseteq X.|Orb(x)|— количество элементов в орбите называется длиной орбиты.

Лемма 3. Две орбиты либо совпадают, либо не пересекаются.

Доказательство. Аналогично тому, что мы делали со смежными классами: пусть элементz \in Xпринадлежит двум орбитамOrb(x)иOrb(y). Тогда существуют такиеg_x, g_y \in G, чтоz = g_xx = g_yy. Тогдаx = g^{-1}_xg_yy. Для любогоx' = gx \in Orb(x)имеемx' = gx = (gg^{-1}_xg_x)y \in Orb(y). Стало быть,Orb(x) \subseteq Orb(y). Аналогично доказываем, чтоOrb(y) \subseteq Orb(x), откуда следует, чтоOrb(x) = Orb(y).\blacksquare

Также заметим, что любой элементx \in Xпринадлежит какой-то орбите, а именноx \in Orb(x). Таким образом, множествоXполностью разбивается на непересекающиеся орбиты, которые можно считать классами эквивалентности. Для любых двух элементовXиз одной орбиты один из них можно перевести в другой при помощи какого-то преобразования изG. Однако, в отличие от смежных классов, длины двух разных орбит могут отличаться.

Пример 1. В качествеGможно взять множество линейных функций видаax+b, гдеa, b \in \mathbb{Q}иa \ne 0(это чтобы для каждой функцииax+bсуществовала обратнаяx/a - b/a), операция*— это композиция функций

(a_1x+b_1)*(a_2x+b_2) = a_1(a_2x+b_2)+a_2 = a_1a_2x + a_1b_2 + a_2

ЗаXможно взять множество всех вещественных чисел\mathbb{R}.g \oplus x— это значение линейной функцииgв точкеx. Орбит бесконечно много, поскольку вGсчётное число элементов, а вX— континуум.

Пример 2. ПустьG— множество невырожденных вещественных матриц размераn \times n, аX— множество всех вещественных векторов длиныn. Операции*и\oplus— это умножение матриц и умножение матрицы на вектор соответственно. Орбиты две. В одной из них нулевой вектор, в другой — все остальные векторы. В похожей модели (дляn=4) крутятся, сдвигаются и масштабируются трёхмерные модели в компьютерной графике.

Картинка взята отсюда
Картинка взята отсюда

Пример 3.G— всевозможные комбинации поворотов и параллельных сдвигов трёхмерного пространства на себя, аX— всевозможные положения чайников в этом пространстве. Чайники не обязательно одинаковые по форме (могут встречаться и какие-нибудь кастрюли и ковшики, которые иногда тоже используются как шляпы чайники). Ну и для двух элементов изXнаша группаGбудет помогать определить — это один и тот же чайник в разных положениях в пространстве, или чайники точно разные. Для каждого чайника — своя личная орбита. Так как среди действий группы нет отражения относительно плоскости, несимметричный чайник и его зеркальное отражение будут находиться в разных орбитах.

Пример 4.G— всевозможные повороты плоскости относительно начала координат, аX— все точки этой плоскости. Орбитами будут окружности всевозможных радиусов с центром в начале координат. Ну и еще начало координат в своей личной орбите.

Пример 5. Предыдущие примеры были не совсем корректные в рамках данной статьи в том плане, чтоGиXбыли бесконечными. Мы все таки хотим работать с конечными объектами и симметриями в множествеX. Для этого модифицируем Пример 2. Возьмём вGтолько такие матрицыn \times n, в которых в каждой строке и каждом столбце ровно одна единица, а в остальных ячейках — нули (это, кстати, подгруппа группы всех невырожденных матриц). ВXвозьмём только 0/1-вектора длиныn. Тогда оба множества будут конечного порядка. Нетрудно заметить, чтоGбудет изоморфно множеству перестановокS_nдлиныn, и, действуя элементами этой группы на элементы изX, мы будем каким-то образом перемешивать числа внутри соответствующих векторов. Количество орбит равноn+1, в одной орбите все вектора с одним и тем же количеством единиц. Вi-й орбите будет ровноC_n^iвекторов.

Пример 6 совсем игрушечный, с которым мы будем работать дальше. ПустьX— все 16 таблиц2 \times 2, каждая из ячеек которой покрашена либо в белый, либо в чёрный цвет. Будем считать две таблицы одинаковыми, если одну из них можно перевести в другую перестановками строк и столбцов. Это последнее правило позволяет нам построить группу преобразованийG, в которой будет 4 элемента:

  1. Нейтральный элемент

  2. Обмен строк

  3. Обмен столбцов

  4. Композиция элементов 2 и 3, когда мы обмениваем ячейки таблицы крест-накрест

Результат действия каждого элемента группыGна каждый элемент множестваXможно показать в виде следующей таблицы:

Действие группы на множестве
Действие группы на множестве

Аккуратно проанализировав все 16 раскрасок, можно разбить их все на следующие 7 орбит:

Орбиты
Орбиты

Лемма Бернсайда и теорема Пойа

Стабилизатором элементаx \in X, на который действует группаG, называется подмножество элементовG, которые не меняютx:St(x) = \{ g \in G : gx = x \}.

Множеством неподвижных точек для элементаg \in Gв множествеXназывается подмножество элементовX, которые не меняются под действиемg:X^g = \{ x \in X : gx = x \}.

В нашем игрушечном примере в каждой строке таблицы красными кружочками я отметил множества неподвижных точек для каждого элемента группы, а в каждом столбце — элементы множестваX, к которым применяются действия из стабилизатора для соответствующего элемента изX. Также в отдельных строке и столбце я указал размеры соответствующих множеств.

Множества неподвижных точек и их связь со стабилизаторами
Множества неподвижных точек и их связь со стабилизаторами

Лемма 4. Имеет место тождество

\sum_{g \in G} |X^g| = \sum_{x \in X} |St(x)|.

Доказательство. Левая часть равенства — это красные кружочки, просуммированные по строкам, а правая часть — они же, просуммированные по столбцам.\blacksquare

Лемма 5. В рамках действия группыGдля любого элементаx \in Xверно

|St(x)| \cdot |Orb(x)| = |G|.

Доказательство. Сначала заметим, чтоSt(x)— это подгруппаG. В этом легко удостовериться, проверив все аксиомы. Значит, по теореме Лагранжа группаGраспадается на смежные классы по подгруппеSt(x). Выпишем эти классы:

g_1St(x), g_2St(x), \cdots,g_kSt(x),

гдеg_i \in G,g_1 = e, аk = |G| / |St(x)|. Действие каждогоi-го из этих смежных классов на элементxдаёт множество из единственного элемента\{ g_ix \}. Действительно, для любогоg \in St(x)имеемg_igx = g_ix.

Теперь докажем, что для любыхi \ne jвыполняетсяg_ix \ne g_jx. Предположим, что для каких-тоi \ne jвышлоg_ix = g_jx, что эквивалентноg_i^{-1}g_jx = x. Рассмотрим подмножество элементов группыH = g_i St(x) \cup g_j St(x)(оно размера2|St(x)|, так как смежные классы не пересекаются) и применим к нему действиеg_i^{-1}. Получимg_i^{-1}H = St(x) \cup g_i^{-1}g_jSt(x), которое по Лемме 1 всё ещё размера2|St(x)|. Но все действия из этого множества не меняютx, значит, в стабилизаторе должно быть хотя бы в 2 раза больше элементов, чем в нём есть. Противоречие.

Осталось осознать, чтоg_ix— это, собственно, различные элементы орбиты, то естьOrb(x) = \{ g_1x, g_2x, \cdots , g_kx \}, а|Orb(x)| = k = |G| / |St(x)|, как указано выше.\blacksquare

Лемма Бернсайда. Пусть\mathcal{O}— множество орбит, тогда

|\mathcal{O}| = \dfrac{1}{|G|} \sum_{g \in G} |X^g|.

Доказательство. В одну строчку:

|\mathcal{O}| = \sum_{O \in \mathcal{O}}1 = \sum_{O \in \mathcal{O}} \sum_{x \in O} \dfrac{1}{|Orb(x)|} = \sum_{x \in X} \dfrac{1}{|Orb(x)|} = \sum_{x \in X} \dfrac{|St(x)|}{|G|} = \dfrac{1}{|G|} \sum_{g \in G} |X^g|.

Теперь пройдёмся по формуле. Сначала бы проходимся по всем орбитам и всем элементам в орбите назначаем вес1/|Orb(x)|, гдеOrb(x) = O. Это эквивалентно просто суммированию по всем элементамx \in X. Далее мы последовательно применяем сначала Лемму 5, а затем Лемму 4.\blacksquare

Зачем вообще нужна лемма Бернсайда? С её помощью мы можем быстрее посчитать количество орбит когда порядок группыGсильно меньше, чем размер множестваX. Нужно только уметь считать количество неподвижных точек для каждого действия.

ЕслиX— множество каких-то объектов изn"штучек", каждая из этих "штучек" покрашена в один изCцветов (у нас вXлежат всевозможные раскраски объектов), аG— группа перестановок этих самых "штучек" (подгруппа полной группы перестановокS_n), и мы считаем, что два раскрашенных объекта в одной орбите тогда и только тогда, когда одну раскраску можно перевести в другую каким-то элементов изG, то число неподвижных точек считать совсем просто, и ответ можно вычислить при помощи теоремы Пойа.

А перед тем как мы к ней перейдем, отмечу, что дальше до конца статьи речь будет идти только про такиеXиG, что описаны абзацем выше (может эти рассуждения можно и обобщить — но в такую возможность я не вдумывался, так что для других случаев я ничего не гарантирую). Итак,

Теорема Пойа. Пустьp(\sigma)— количество циклов в перестановке\sigma. Тогда

| \mathcal{O} | = \dfrac{1}{|G|} \sum_{\sigma \in G} C^{p(\sigma)}.

Доказательство. Нужно показать, чтоC^{p(\sigma)} = |X^\sigma|. Рассмотрим какой-нибудь цикл в перестановке\sigma. Чтобы элемент множестваXбыл неподвижной точкой, все "штучки", по которым проходит цикл, должны быть покрашены в один цвет — один изCвариантов. Покраски различных циклов независимые, поэтому всего у нас будетC^{p(\sigma)}раскрасок. Нетрудно понять, что каждая из таких раскрасок действительно представляет собой неподвижную точку изX.\blacksquare

Для примера применим Теорему Пойа к нашему игрушечному примеру. У нас вGчетыре перестановки, в одной из них 4 цикла, в трёх остальных — 2. Таблички2 \times 2мы красим в 2 цвета. Применяем формулу:

|\mathcal{O}| = \dfrac{1}{4}(2^4 + 2^2 + 2^2 + 2^2) = \dfrac{1}{4}(16+4+4+4) = \dfrac{28}{4} = 7.

Сошлось!

Пу-пу-пу
Пу-пу-пу

Перед тем, как это всё расписывать, я думал, что писанины будет раза в 2 меньше. Впрочем, теперь я могу вывести и доказать лемму Бернсайда с нуля, даже если меня разбудить посреди ночи...

Количество орбит шляп

Теперь можно применить теорему Пойа к нашей задачи имени Винтика и Шпунтика. У нас объекты вX— это параллелепипеды размераn \times m \times k, каждая ячейка которых покрашена в один из двух цветов, и у нас2^{nmk}таких параллелепипедов. ВGу нас всевозможные действия, которые перемешивают слои параллелепипедов.|G| = n!m!k!, так как мы можем независимо перемешивать слои по каждой из трёх осей координат, ну и, в принципе, у насGизоморфна прямому произведению полных групп перестановокS_n \times S_m \times S_k. Следующий код вычисляет количество орбит по формуле:

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

\approx 10^{12}

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

Сложность решения задачи до — это значениеnmk+nm, так как алгоритм из предыдущей части вычисляет формулу за времяO^*(2^{nmk+nm}), сложность решения после — это значение\log_2 |\mathcal{O}| + nm, так как теперь ожидаемое время работы будетO^*(|\mathcal{O}| \cdot 2^{nm}).

В задаче(2,3,12,12)группа получается слишком большая, поэтому вычисления количества орбит я для этой задачи не дождался. Впрочем, это значение можно оценить числом2^{nmk}/(n!m!k!)снизу — это самое большое слагаемое в формуле теоремы Пойа. Точное число орбит получается примерно того же порядка, так как остальные слагаемые на самом деле очень маленькие по сравнению с этим самым большим.

И вот, например, для задачи(4,4,4,4)программа из предыдущей части считала бы ответ около 24 миллиардов лет. Теперь мы можем посчитать его "всего" за 2 миллиона лет. Прогресс налицо. Мы можем еще добавить в группу действия, которые вращают шляпу4 \times 4 \times 4(ну или, что то же самое, меняют оси координат местами). Ускорение в 6 раз, итого — 300 тысяч лет. Но это программа на Python, если её переписать на C, хардкорно оптимизировать, а затем запустить параллельные вычисления на десятке видеокарт, то можно уложиться лет за 5. То есть, в принципе, задача уже решаемая.

Это все, конечно, хорошо, но на самом деле знание о количестве орбит не сильно помогает решить нам нашу задачу. Нам нужны сами орбиты.

Генерация орбит

Как генерировать орбиты? Один из самых простых способов следующий. Можно взять все элементыXи представить их как вершины графа. При помощи действий изGдля любогоx \in Xможно находить соседей, и таким образом проводить между вершинами рёбра. Если группаGбольшая, то можно найти в ней генераторы или порождающие элементы. Это такое подмножество действийG, через которые можно выразить все остальные действия. Когда граф построен — нужно выделить в нём компоненты связности, это и будут орбиты. Минус такого подхода в том, что нужно очень много памяти для хранения всех посещённых вершин.

Другая идея состоит в том, чтобы выделить из каждой орбиты ровно одного представителя. Обычно это делается так: если объекты изXсостоят изn"штучек", каждая из которых красится в один изCцветов, то можно на этихn"штучках" задать порядок, а цвета пронумеровать числами, скажем, от0доC-1. Тогда каждому объекту изXможно поставить в соответствиеn-значное число вC-ичной системе счисления (или строку длиныnс алфавитом изCсимволов). Представителем орбиты будет объект с максимальным числом (или с лексикографически максимальной строкой) по всем объектам в орбите. Будем называть такой объект максимальным. Теперь можно просто перебрать все объекты изX, проверить максимальны ли они и отсеять все объекты, которые не максимальны. Таким образом можно получить ровно по одному представителю из каждой орбиты.

Как проверить, что объект максимальный? Нужно попробовать применить к нему все действия из группы. Если после какого-то действия в результате получится объект больше — то объект не максимальный. Если увеличения объекта не произошло для всех действий группы — он максимальный.

Посчитать количество объектов в орбите без затрат памяти тоже на самом деле не сложно. Нужно воспользоваться Леммой 5 и посчитать|St(x)|, то есть количество действий группы, которые переводят объект сам в себя. Тогда длина орбиты|Orb(x)|будет равна|G|/|St(x)|.

Таким же способом можно просто искать максимальный объект в орбите. Возьмём какой-то объект, применим к нему действия группы всеми способами и возьмём максимум из того, что получилось. Например, если группа — это множество всех перестановокS_n, то заO(n!n)можно найти максимальный объект в орбите, в котором все "штучки" упорядочены в порядке невозрастания. Ой, это же BogoSort. Человечеству известны чуть более быстрые алгоритмы сортировки, так что на самом деле в некоторых случаях искать максимальный объект в орбите можно быстрее. Но об этом поговорим в следующем параграфе.

Итого все орбиты (как множество представителей) и их длины можно найти без затрат памяти заO^*(|G| \cdot |X|). Всякие хитрые отсечения, когда мы сначала пробуем такие действия группы, которые потенциально преобразуют объект в объект побольше (чтобы эффективно отсекать не максимальные объекты), помогают ускорить процесс. Но есть способ еще быстрее.

Будем генерировать объекты изXне сразу полностью, а пошагово. Например, будем последовательно красить в каждый изCцветов "штучки" в объекте в порядке приоритета лексикографического сравнения (то есть сначала самую значимую "штучку", потом следующую, и так далее). У нас получится рекурсивное дерево поиска, в каждой вершине которого будет частично покрашенный объект. Полностью покрашенные объекты — это частный случай частично покрашенных, такие объекты будут находиться в листах дерева.

Будем называть частично покрашенный объект максимальным, если максимальным является полностью покрашенный объект, который мы можем получить из частично покрашенного путём покраски не покрашенных "штучек" в минимальный цвет изCимеющихся.

Лемма 6. Если частично покрашенный объект не является максимальным, то не являются максимальными также все объекты в его поддереве.

Доказательство. Пусть в частично покрашенном объектеxопределены цвета для первыхk"штучек" изn. Тогда по правилу покраски частично покрашенных объектов все "штучки" с номерами большеkпокрашены в минимальный цвет. Пусть также есть действие группы (перестановка), которое переводитxвx', который лексикографически большеx(это, собственно, критерий того, чтоxне максимален). Это означает, что есть "штучка" под номеромi, которая покрашена вx'в больший цвет, чем вx, а для всех номеров "штучек" меньшеiих цвета в двух объектах совпадают.

Предположим, чтоi > k. Тогда вx'префикс длиныkсовпадает с префиксом той же длины вx. И количество "штучек", которые покрашены в цвет больше минимального — тоже совпадает. Но на позицииiвx'"штучка" тоже покрашена в цвет больше минимального (иначе она не может быть большеi-ой "штучки" вx). Итого у нас вx'"штучек" не минимального цвета больше, чем вx, что невозможно, так как действие группы — перестановка.

Значит,i \le k. Тогда всевозможные раскраскиxпоследнихn-kштучек (которые мы временно покрасили в минимальный цвет) не влияют на префикс длиныiвx. Вx'же "штучки" с неопределёнными цветами могут быть на позициях меньшеi, но тогда при всевозможных раскрасках этих "штучек" мы будем получать объекты лексикографически не меньшеx', которое строго большеx.\blacksquare

Согласно Лемме 6, если в результате рекурсивного обхода дерева мы наткнёмся на не максимальную вершину, то нам следует откатиться, ибо во всём поддереве нам ловить нечего. Напротив, если частично покрашенный объект максимален — в его поддереве существует максимальный лист. Например, объект, в котором все не прокрашенные "штучки" покрашены в минимальный цвет.

Из предыдущего абзаца следует, что в дереве не большеn|\mathcal{O}|максимальных вершин. Действительно, для каждой максимальной вершины существуют цепочки от неё до корня, а также от неё до какого-то листа. Значит, наше дерево — это объединение|\mathcal{O}|цепочек длиныn(если совсем строго, то длиныn+1, но корнем дерева мы можем пренебречь). Тогда количество вершин, для которых мы проверяем максимальность не превышаетCn|\mathcal{O}|, что, как правило, сильно меньше|X|. На практике рассматриваемых вершин получается и того меньше, когда ветвление дерева по максимальным вершинам происходит на больших глубинах (этим даже можно управлять, нужным образом задавая порядок "штучек" для лексикографического сравнения).

Алгоритм можно дополнительно ускорить, используя секретную технику шаолиньских монахов, которую я называю халтурная проверка максимальности. Будем тестировать объекты на максимальность не всеми действиями группы, а только некоторыми, выбранными наугад. Тогда мы сэкономим время на проверку объектов, которые действительно являются максимальными (напомню, что на них надо испробовать все действия группы, чтобы убедиться в максимальности). Не максимальные объекты будут отсеиваться лишь с некоторой вероятностью, и мы будем вынуждены проверять некоторые дополнительные "лишние" не максимальные объекты. Но принцип здесь такой: пусть мы будем проверять в 10 раз больше объектов, зато каждая проверка будет в 100 раз быстрее, тогда в итоге получится ускорение в 10 раз. Понятно, что листы дерева поиска надо будет проверять полностью без всякой халтуры.

Работу алгоритма можно проиллюстрировать следующей картинкой для нашего игрушечного примера:

Синим цветом показаны не покрашенные "штучки"
Синим цветом показаны не покрашенные "штучки"

Как можно видеть, в этом дереве 7 листов, что совпадает с числом орбит.

Эксплуатируем структуру группы

Как было отмечено в предыдущем параграфе, иногда проверять максимальность объекта можно быстрее, чем заO^*(|G|). В этом параграфе мы разберём как ускорить процедуру обработки вершин на примере шляп (это которые параллелепипедыn \times m \times k).

Чуть выше уже упоминалось, чтоGу нас изоморфнаS_n \times S_m \times S_k. То есть каждое действие можно представить как последовательность из трёх действий: сначала перемешаем слои шляпы, перпендикулярные осиx(n!способов), потом перпендикулярные осиy(тутm!способов), и, наконец, перпендикулярные осиz(способов —k!).

Вот именно на последней перестановке мы и сэкономим! А сэкономим как и описано выше — просто отсортируем уже перемешанные двумя первыми перестановками слои в порядке убывания. Порядок битов для лексикографического сравнения должен быть соответствующий: из более раннего по порядку слоя биты должны быть более значимы, чем из более позднего. Также биты должны быть одинакового упорядочены по значимости в каждом слое.

Итого получаем сложностьO^*(n!m!). Думаю, можно еще ускорить алгоритм более изощрёнными техниками, но мы остановимся пока на этом. Код проверки на максимальность:

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 — это список изkслоёв, каждый из которых задан битовой маской длиныn m. Порядок переменных немного неудобен с точки зрения программирования — было бы логичней, если бы шляпа разбивалась наnслоёв. Но так какn \le k, разбиение именно что наkслоёв даёт большее ускорение.

Подсчёт порядка стабилизатора тоже можно ускорить доO^*(n!m!). Действительно, пусть мы как-то перемешали слои по первым двум осям координат. Тогда по третьей оси координат нам опять нужно просто отсортировать все слои (мы предполагаем, что считаем порядок стабилизатора для максимального объекта). Если после сортировки получился другой список — тут элементов стабилизатора нет. Если списки совпали — то хотя бы один элемент стабилизатора есть, но их может быть больше одного. Чтобы найти их количество, нужно разбить все слои шляпы на группы одинаковых, взять факториалы размеров полученных групп, а потом всё перемножить. Код (сразу для вычисления длины орбиты):

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 можно было бы вынести наружу из цикла, но тут рядом сортировка (а ещё сборка новой шляпы заO(nmk)), так что сложность реализации это не сильно портит.

Остаток кода и результаты запуска

Собственно, код ниже. Недостающие функции можно выковырять из текста статьи выше.

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 строит дерево перебора шляп. Для листа дерева (когда шляпа полностью сгенерирована) запускается вычисление функции\mathcal{F}(в коде это calcF), а также вычисляется размер класса эквивалентности (длина орбиты) для данной шляпы. Шляпы же в процессе рекурсивного обходя дерева строятся не побитово, а послойно. Также включены две дополнительные проверки, чтобы уменьшить количество запусков функции is_maximal. Первая из них (строка 82) заключается в том, что маски в слоях шляпы должны идти в порядке невозрастания — иначе мы можем поменять местами слои и получить шляпу больше. Вторая проверка (строка 83) использует посчитанную ранее таблицу max_mask: если маску в текущем слое можно преобразовать в другую, которая больше маски в самом первом слое шляпы, то можно и всю шляпу "перетасовать" этим же преобразованием, а затем перенести маску текущего слоя в самый первый слой. И тогда получится шляпа больше.

В результате этим кодом удалось посчитать ответ для задачи(3,3,4,12)за 3 часа (мы уже решили эту задачу в прошлой части, но ценой 1 месяца вычислений; ответы совпали), задачи(2,3,7,42)за 3.3 часа,(2,4,5,20)за 9 часов,(2,3,8,24)за 29.5 часов, а также(2,3,9,18)за 256 часов (почти 11 дней). Какой ответ получился — в сводной таблице ниже.

Примечание. Уже в процессе написания статьи, я нашел в коде баг, где вычислялся список max_mask (тут уже исправленная версия). Этот баг не влияет на ответ, но может повлиять на время вычислений (мне было лень перезапускать расчеты). Из-за него по сути отсечение в строке 83 почти не работало, что приводило к "лишним" запускам полной проверки максимальности. Так что, возможно, на самом деле код выше считает ответ несколько быстрее.

Кто следующий?
Кто следующий?

Замечательная симметрия задач (2, *, *, *)

В ходе исследования получаемых орбит (в целях уменьшения их числа) был замечен и доказан следующий факт:

Лемма 7. Если в шляпе размера2 \times m \times kесть столбик вида\begin{smallmatrix}0\\1\end{smallmatrix}, то при изменении его на\begin{smallmatrix}1\\0\end{smallmatrix}значение функции\mathcal{F}не меняется.

Доказательство. Вспомним структуру функции\mathcal{F}, которая описана во второй части. Она полностью зависит от функцииdp, которая выглядит так:

dp(h,a) = \sum_{b \in B} \sum_{c \in C} s(b,c) F(h,a,b,c).

Я только заменил вот этиa''иA^{***}на аналоги без штрихов. Этаdpсоответствует некоторому срезу1 \times m \times 2одного из слоёв четырёхмерного гиперпараллелепипеда. То естьb— это маска изmбитов, аc— маска из 2 битов. Преобразуем формулу, разрезав этот срез на столбики1 \times 1 \times 2всё тем же трюком, которым во второй части сворачивали всю формулу. Получим:

dp(h,a) = \sum_{c \in C} s(c) \prod_{i \in [1,m]} \left( \sum_{b' \in B'} s(b')F(h^i,a^i,b',c) \right),

гдеh^iиa^i— это кусочки масокhиaдляi-го столбика, аb'— один из двух вариантов одного бита. Теперь вспомним, что такоеF. Мы для всех кубиков внутри столбика вычисляем сумму битов, которые на этот кубик смотрят, а затем перемножаем по всем кубикам. То есть вся функция\mathcal{F}в своём фундаменте зависит от выражений внутри скобок, которые расписываются как

(h_1+a_1+c_1+1)(h_2+a_2+c_2+1)-(h_1+a_1+c_1)(h_2+a_2+c_2),

гдeh_i, a_i, c_i— это биты, которые "пришли" вi-й кубик. Используя замену\alpha = h_1 + a_1 + c_1и\beta = h_2 + a_2 + c_2, получим

(\alpha+1)(\beta+1) - \alpha \beta = \alpha \beta + \alpha + \beta + 1 - \alpha \beta = \alpha + \beta + 1 == (h_1+a_1+c_1) + (h_2+a_2+c_2)+1=(h_1+h_2)+(a_1+a_2)+(c_1+c_2)+1,

то есть значение\mathcal{F}не зависит от порядкаh_1иh_2(как и от порядка других пар битов).\blacksquare

Таким образом, мы даже доказали чуть больше: если поменять в шляпе местами два бита в любом столбике высоты 2, то значение\mathcal{F}не изменится. И... размер группы у нас возрастает с2m!k!доm!k!2^{mk}. С соответствующим уменьшением числа орбит. Конечно же, с такой большой группой мы работать не будем, вместо этого рассмотрим троичные шляпы размераm \times k. Каждому 0 в такой троичной шляпе мы поставим в соответствие столбик\begin{smallmatrix}0\\0\end{smallmatrix}в обычной шляпе, каждой 1 — либо\begin{smallmatrix}0\\1\end{smallmatrix}, либо\begin{smallmatrix}1\\0\end{smallmatrix}, а каждой 2 — столбик\begin{smallmatrix}1\\1\end{smallmatrix}. Каждой троичной шляпе будет соответствовать ровно2^zобычных шляп, гдеz— количество единиц в троичной шляпе.

Я на скорую руку переделал код под задачи вида(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 и выше эта идея не работает. Там получается

(\alpha+1)(\beta+1)(\gamma+1)-\alpha\beta\gamma = \alpha\beta+\alpha\gamma+\beta\gamma+\alpha+\beta+\gamma+1,

и вот эти вот произведения портят всю малину, и неясно что с ними делать.

Сводные таблички

Сначала таблица по количеству орбит, которая частично повторяет таблицу выше.

Задача

Количество шляп

Количество орбит

Орбит троичных шляп

(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

\approx 10^{12}

\approx 5 \times 10^8

(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

Для задачи(2,3,12,12)опять число приблизительное из-за большого размера группы, но тут уже оно получилось в результате прямого вычисления всей задачи. В некотором смысле перечислить все орбиты оказалось быстрее, чем посчитать их количество через теорему Пойа. Но точное число орбит я не записал, только последняя строчка в терминале показала, что их число где-то между452 \times 10^6и453 \times 10^6.

Теперь таблица с примерными сложностями задач и временем их решения:

Задача

Слж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 — ускоренный для задач вида(2,*,*,*). Там, где звёздочка — оценочное время работы, эти задачи до конца не досчитались.

Как можно видеть, удалось решить ещё 5 задач, итого 10 из 14. Решение задачи(2,3,7,42)ускорилось с гипотетических 8 месяцев до одной минуты! Для других задач время можно оценить следующим способом: +1 к сложности увеличивает время счёта примерно в 2 раза, +10 к сложности — примерно в 1000 раз.

В следующей таблице посчитанные ответы на посчитанные задачи. Так как числа слишком большие, я разбил их на блоки по 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, но до решения задачи(4,4,4,4)пока далековато. На текущий момент у меня закончились эффективные математические и алгоритмические идеи по ускорению вычислений. И пока что дальнейшее продвижение видится только в аккуратном переписывании программы на C/C++, распараллеливание на видеокарты и последующие нудные технические оптимизации. Так что если следующая часть и будет, то весьма не скоро. Ну или вдруг в голову нагрянет какая-нибудь свежая идея.

Возможный другой путь дальнейшего развития событий: поиск дополнительных неочевидных симметрий. При этом не обязательно в виде добавления новых действий в группу, можно уже имеющиеся орбиты каким-то образом склеить в кластеры шляп с одинаковым значением функции\mathcal{F}, и тут уже принцип склеивания будет зависеть от структуры самих шляп. Примерно это происходило в статье про подсчет количества судоку. Эксперименты показывают, что орбиты, дающие одинаковые значения\mathcal{F}есть, их много, но я не смог разглядеть принципа их склеивания вместе (если бы разглядел — он и вот эта симметрия для задач(2,*,*,*)перекочевали бы в четвёртую часть). В общем, нужны ещё симметрии.

Где симметрии, Билли? Нам нужны симметрии
Где симметрии, Билли? Нам нужны симметрии

А ещё можно поугадывать формулу. Но тут всё совсем неопределённо.

Теги:
Хабы:
+22
Комментарии0

Публикации

Ближайшие события