Как стать автором
Обновить

Винтик и Шпунтик, часть 1: формула включений-исключений

Уровень сложностиСредний
Время на прочтение14 мин
Количество просмотров1.6K
Винтик и Шпунтик договариваются о стратегии
Винтик и Шпунтик договариваются о стратегии

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

Описывать все буду достаточно подробно с самого начала, чтобы было понятно даже людям не совсем "в теме". Однако, такие подробности не только для читателей, сколько для себя. Для того, чтобы разложить у себя в голове всё последовательно и по полочкам. И ничего не пропустить.

Постановка задачи и совершенные паросочетания

Формулировка задачи в редакции челленджа звучит следующим образом:

«Незнайка записывает 9 разрядов 10-значного десятичного числа и пропускает один по своему выбору. Пропущенный разряд он предлагает записать Винтику, а затем показывает полученное 10-значное число Шпунтику. Сколькими способами могут Винтик и Шпунтик договориться, чтобы Шпунтик угадал, какой именно разряд записал Винтик?»

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

Давайте разберёмся с "ослабленной" версии задачи, когда Незнайка записывает всего лишь 2-значное число с пропуском (в двоичной системе счисления). Варианты того, что мог записать Незнайка:0*,1*,*0,*1. Всего 4 варианта. Винтик может переделать0*либо в00, либо в01.1*— либо в10, либо в11. И так далее. Варианты того, что может сделать Винтик в каждом из случаев, можно нарисовать в виде двудольного графа, где каждая вершина в левой доле соответствует тому, что написал Незнайка, а в правой — тому, во что это может превратить Винтик. Причём рёбра в этом графе — варианты действий Винтика.

Двудольный граф
Двудольный граф

Теперь, после заполнения пропуска, Шпунтику доступен один из вариантов00,01,10,11. И по нему Шпунтику нужно определить какое число с пропуском было изначально. Причем он может выбирать только между теми вариантами, откуда из левой доли приходят рёбра.

Заметим, что в обоих долях вершин поровну, поэтому чтобы Шпунтику не ошибиться (с каким бы числом с пропуском не пришел Незнайка), на любые два варианта, что видит Шпунтик, ответы должны быть различны. Действительно, если на какие-то два "входа" для Шпунтика соответствуют одной и той же вершине слева, то какая-то вершина слева вовсе не будет "покрыта" возможными ответами Шпунтика. И как быть, если Незнайка запишет именно этот не покрытый вариант? Если же для любых двух "входов" для Шпунтика ответы различны — то любой вариант, с каким бы не пришел Незнайка, будет покрыт, и Шпунтик его угадает. Дело осталось за малым: Винтику и Шпунтику нужно договориться так, чтобы Винтик вставлял цифру в соответствии с "табличкой" всевозможных ответов Шпунтика.

В общем и целом, для Винтика и Шпунтика необходимо и достаточно какое-то взаимно-однозначное соответствие между вершинами левой и правой доли графа, причём каждую пару вершин в этом соответствии должно соединять ребро графа. Такая конструкция называется совершенным паросочетанием. Совершенными они называются потому, что покрывают все вершины графа. Ну и количество способов договориться — это количество таких попарно различных паросочетаний. Например, для нашего "ослабленного" бинарного случая совершенных паросочетаний ровно два:

Два совершенных паросочетания
Два совершенных паросочетания

В общей, не "ослабленной", постановке задачи у нас и в левой, и в правой долях по10^{10}вершин. Для промежуточных (так называемыхN-арных) версий задачи у нас в обеих долях поN^Nвершин. Бинарный и тернарный варианты задачи решаются достаточно легко, как показал сам @vvvphoenix. А вот уже с кватернарным случаем возникают проблемы.

Количество совершенных паросочетаний и перманент

В принципе, задачу можно попытаться решить перебором: для каждой вершины левой доли выберем исходящее ребро всеми возможными способами, а затем проверим, что все вершины правой доли покрыты ровно одним ребром. Получим решение заO^*(N^{N^N}) (здесь и далееO^*(f(n))означаетO(f(n) \cdot Poly(n)), гдеPoly(n)— какой-то полином отn). Даже дляN=3получается астрономическая сложность. Впрочем, в перебор вариантов можно добавить некоторые отсечения: например, откатываться, как только какая-либо вершина из правой доли покрыта двумя выбранными ребрами. Тогда случайN=3можно решить очень быстро, но для большихNвозникнут сложности: если мы будем перечислять все варианты, то нам в любом случае потребуется количество операций не меньше, чем количество вариантов.

Однако, чтобы найти ответ, можно не перечислять все варианты по одному. Один из вариантов так сделать: динамическое программирование.

Обозначим множество вершин левой доли графа заL, а правой — заR. Пустьdp(X)дляX \subseteq R— количество частичных паросочетаний, которые полностью покрывают|X|первых вершин левой долиL(для этого вершины левой доли надо заранее пронумеровать:l_1, l_2, \ldots , l_{|R|}) и все вершины вXиз правой долиR. Тогдаdp( \not O ) = 1(упс) и

dp(X) = \sum_{\substack{r \in X \\ (l_{|X|}, r) \in E}} dp(X \setminus \{ r \})

Суть формулы достаточно проста: мы всеми способами убираем "последнее" ребро изX, сводя задачу к чуть меньшей по размеру. Ответ — этоdp(R). Итого получаем решение заO^*(2^{N^N}), что заметно быстрее перебора. Единственный недостаток этого решения — нам нужноO(2^{N^N})памяти для хранения состояний. Хорошая новость состоит в том, что есть решение, лишенное данного недостатка — это формула включений-исключений, о которой чуть ниже.

Также немного упомянем такую штуку, как перманент. Построим матрицуA, в которой на пересеченииi-й строкиj-го столбца будет1, еслиi-я вершина левой доли иj-я вершина правой доли соединены ребром. То есть матрицаA— это такой компактный способ записать ребра двудольного графа. Например, для нашего случаяN=2матрица выглядит следующим образом:

A = \left( \begin{matrix} 1 & 1 & 0 & 0 \\ 0 & 0 & 1 & 1 \\ 1 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 \end{matrix} \right)

По сути эта та самая "табличка Шмелёва" из публикации @vvvphoenix. Перманент матрицы находится по следующей формуле (полагая, чтоAимеет размерn \times n):

\mathbf{perm} A = \sum_{\sigma \in S_n} \prod_{i=1}^n A_{i,\sigma_o}

Мы перебираем все перестановки\sigma, которые по сути соответствуют всевозможным совершенным паросочетаниям в полном двудольном графе, а затем суммируем веса всех этих паросочетаний. Здесь вес паросочетания — это произведение весов ребер, которые берутся из матрицаA. Таким образом, если паросочетание действительно допустимое для матрицыA(все ребра "на месте" с весом1), то к сумме прибавится1. Иначе, если хотя бы одного ребра нет — вес паросочетания будет равен0.

Проблема в том, что для произвольных матриц (и даже для0,1-матриц) перманент быстро (за полиномиальное время) считать никто не умеет. Его можно вычислить по определению выше заO^*(n!), однако есть способ чуть быстрее — через то же самое динамическое программирование заO^*(2^n), что описано выше. Разве что формула чуть-чуть изменится:

dp(X) = \sum_{r_i \in X} A_{|X|,i} \times dp( X \setminus \{ r_i \} )

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

Формула включений-исключений и её доказательство

Применительно к нашей задаче решение через формулу включения-исключения описывается в слайдах лекции по ссылке. Саму лекцию я не видел, поэтому восстановлю тут необходимую нам информацию по имеющимся картинкам.

Слайд с формулой включений-исключений (и с её доказательством) следующий:

Формула включений-исключений и её доказательство
Формула включений-исключений и её доказательство

У нас просто есть две какие-то функцииf(X)иg(X), которые возвращают какие-то числа для каждого подмножестваX \subseteq A. При этомf(X)можно выразить черезg(X)согласно формуле на слайде. Тогда, как утверждает Лемма, мы можем обратить вычисление функций: наоборот, выразитьg(X)черезf(X).

Когда это может кому-то понадобиться? Например, в случаях, когдаg(X)считается долго, сложно и неясно как (и именно её нам надо посчитать для решения задачи), а вотf(X)решается просто, быстро и понятно.

Теперь перейдём к доказательству. Первый переход в доказательстве — это просто подстановка выражения функцииfчерез функциюg. Во втором переходе мы группируем все слагаемые по входящему туда множителюg(Z), после чего в каждой группе выносим этот множитель за скобку. Третий переход самый сложный для понимания. Там утверждается (в сноске ниже), что все слагаемые (кромеg(Z)приZ=X) будут нулевые. Проиллюстрируем этот момент картинкой:

Множество Y "зажато" между X и Z
Множество Y "зажато" между X и Z

Итак, еслиZ \ne X, то мы берем сумму по всем множествамY, "зажатым" междуXиZ. В частности, слагаемых в сумме2^{|X-Z|}. Давайте возьмём в "зазоре" междуZиXкакой-нибудь элементv. Тогда всевозможные множестваYможно разбить на пары. В каждой паре множестваY_1иY_2почти одинаковы, единственное отличие: вY_1vвходит, а вY_2— нет. То есть,Y_1 = Y_2 \cup \{ v \}. Одно из этих множеств будет давать вклад в общую сумму+1, а другое-1, то есть итоговый вклад0. И так по всем парам.

В случаеZ = Xслагаемое в сумме по "зажатым"Yоказывается ровно одно, и оно равно1. Именно благодаря ему до самого конца "доживает" единственное слагаемое внешней суммыg(X).

Применение формулы к нашей задаче

Теперь нам нужно подобрать подходящие функцииf(X)иg(X), которые можно "приложить" к нашей задаче. Мы будем рассматривать покраски рёбер двудольного графа следующего вида: для каждой вершины из левой долиLмы будем выбирать ровно одно исходящее из неё ребро и красить его в красный цвет. При этом в некоторые вершины правой долиRв итоге могут входить несколько покрашенных рёбер, а некоторые — ни одного. Доли графа равномощны, то есть|L| = |R|, и в каждой раскраске у нас будет покрашено в красный цвет ровно|R|рёбер.

Примеры покраски рёбер какого-то двудольного графа
Примеры покраски рёбер какого-то двудольного графа

Будем называть вершинуvиз правой долиRпокрытой, если в неё входит хотя бы одно покрашенное ребро. Аналогично, будем называть вершину не покрытой, если в неё не входит ни одного покрашенного ребра.

Теперь для каждого подмножестваXвершин правой долиRопределим функциюg(X)как количество раскрасок рёбер (вида, описанного выше), в которых все вершины изXпокрыты, а все вершины не ихX(то есть изR \setminus X) — не покрыты.

Соответственно, для каждого подмножестваX \subseteq Rопределим функциюf(X)как количество допустимых раскрасок, в которых все вершины не изXне покрыты (некоторые вершины изXпри этом могут быть покрыты, а остальные — не покрыты).

Несложно убедиться, чтоf(X) = \sum_{Y \subseteq X} g(Y). Действительно, рассмотрим все допустимые раскраски, которые считаетf(X), а затем сгруппируем их: в каждой группе будут все способы, в которых множество покрытых вершин правой доли в точности равноY (а остальные вершины изXне покрыты). Но количество раскрасок в каждой такой группеY— это в точностиg(Y).

Ответом на нашу задачу будет g(R). И вот почему. В каждой раскраске, которую учитываетg(R), будут учтены все те способы, в которых все|R|вершин из правой доли покрыты. Но у нас в каждой раскраске ровно|R|покрашенных рёбер. Значит каждая вершина правой доли будет покрыта ровно одним красным ребром. А это, собственно, означает, что рассматриваемые раскраски функциейg(R)являются паросочетаниями.

Отлично, теперь мы можем через формулу включений-исключений выразитьg(R)через знакопеременную сумму значенийf(X). Как считатьf(X)? А очень просто.

Пусть даноX— это множество "разрешённых" вершин, куда можно проводить покрашенные рёбра (а можно и не проводить). А вот вершины множестваR \setminus X— "запрещённые". Туда нельзя проводить покрашенные рёбра ни при каких обстоятельствах.

Теперь мы считаем количество покрасок так: для каждой вершины левой доли (изL) мы смотрим во сколько разрешённых вершин правой доли мы можем провести ребро. Это, собственно, количество способов покрасить исходящее ребро из данной вершины левой доли. Теперь просто перемножаем эти количества способов. Это и есть значениеf(X).

Пример вычисления функции f(X)
Пример вычисления функции f(X)

Например, пусть для графа, показанного на картинке, мы считаемf(X), гдеX = \{r_1, r_2\}. То есть первые две вершины правой доли разрешённые, а третья — запрещённая. Тогда для первой вершины левой доли имеется2исходящих ребра, которые можно красить. У второй вершины левой доли — тоже2, а у третьей — только одно. Итого получаемf(X) = 2 \cdot 2 \cdot 1 = 4, эти4варианта показаны на картинке внизу.

Включения и исключения глазами скульптора

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

Формулу включений-исключений можно доказать по-другому. Для удобства дальнейшего изложения введем функцию\overline{f}(X) = f( R \setminus X ). ЗдесьXбудет задавать множество вершин правой доли, которые нельзя покрывать. Также введем значенияQ_iдляiот0до|R|, которые будут обозначать количество способов раскраски рёбер, в которых первыеiвершин правой доли покрыты.Q_{|R|}— это то, что мы ищем.

Очевидно,Q_0 = \overline{f}( \not O ). Также довольно ясно, чтоQ_1 = \overline{f}( \not O ) - \overline{f}( \{ r_1 \} ). Действительно, мы из всех способов вычитаем все те, в которыхr_1не покрыта, и у нас остаются только те способы, гдеr_1покрыта. Это можно изобразить следующей картинкой:

Синим цветом показано множество Q_1
Синим цветом показано множество Q_1

Как теперь вычислитьQ_2? Надо изQ_1вычесть все те варианты, когда вершинаr_1покрыта, а вершинаr_2— нет. Это вон тот верхний синий прямоугольник на картинке. Как посчитать эти варианты? Да точно так же, как иQ_1, нужно только везде в множества добавитьr_2. Получим:

Q_2 = Q_1 - \left(\overline{f}( \{ r_2 \} ) - \overline{f}(\{r_1, r_2\})\right) = \overline{f}( \varnothing ) - \overline{f}(\{r_1\}) - \overline{f}( \{ r_2 \} ) + \overline{f}(\{r_1, r_2\})

Теперь введём в рассмотрение третью вершинуr_3. Картинка теперь станет трёхмерной, и нам нужно из текущего числа способов вычесть вон те в дальнем "кубике":

Может диаграммами Эйлера-Венна было бы нагляднее...
Может диаграммами Эйлера-Венна было бы нагляднее...

Формулу выписывать не буду, но нам нужно просто взятьQ_2, везде в множества добавитьr_3, после чего полученную сумму вычесть изQ_2— и получитсяQ_3. Проделав эту процедуру|R|раз, получим:

Q_{|R|} = \sum_{X \subseteq R} (-1)^{|X|} \overline{f} (X) = \sum_{X \subseteq R} (-1)^{|R|-|X|} f(X)

Это совпадает с формулой, полученной выше.

Плюс данного доказательства в следующем: числаQ_iмы получаем в порядке убывания, так как все способы, соответствующиеQ_{i+1}, входят также в множество способов, соответствующихQ_i. Это позволяет оценивать финальный ответ, посчитав формулу включений-исключений лишь частично. Да, есть способы оценивать ответ точнее, но здесь мы получаем оценку "нахаляву", всего лишь аккуратно выбрав порядок суммирования.

Обобщение "классической" задачи

Давайте напоследок сделаем обобщение изначальной задачи. Для того, чтобы было больше вариантов "входных данных". Решения для обобщений задачи могут в будущем дать какие-нибудь подсказки к решению изначальной задачи.

Будем рассматривать числа с переменной системой счисления. Например, пусть в числеx_1x_2x_3первая цифраx_1будет приниматьa_1различных значений,x_2a_2различных значений, аx_3a_3различных значений. Тогда всего всевозможных чисел у нас будетa_1 \cdot a_2 \cdot a_3.

Примером "чисел" с переменной системой счисления для различных разрядов является запись текущего времени, например 12:13:14. При этом старший "разряд" часов может принимать24различных значений, а другие два "разряда" минут и секунд по60.

Итак, пусть количество способов для каждой изn"цифр" числа задано последовательностью(a_1, a_2, \ldots, a_n). Тогда общее количество всевозможных чисел равноP = a_1 \cdot a_2 \cdot \ldots \cdot a_n. Теперь посчитаем количество чисел, в которых в некоторых разрядах стоят звёздочки. Если звёздочка стоит в первом (старшем) разряде, то количество таких чисел равноa_1 \cdot a_2 \cdot \ldots \cdot a_n = P / a_1. Аналогично, если звездочка стоит вi-м разряде, то количество чисел равноP/a_i. Итого, еслиP^*— это общее количество "звезданутых" чисел, то

P^* = \dfrac{P}{a_1} + \dfrac{P}{a_2} + \ldots + \dfrac{P}{a_n}.

Нас интересуют варианты задачи, гдеP = P^*, чтобы количество вершин в долях графа было одинаково и паросочетания были совершенными (дляP < P^*ответ очевидно равен0, а для случаяP > P^*паросочетания получатся не совершенные, и нужно еще договориться с тем, что же считать для Винтика и Шпунтика "стратегией"). Итак, заменяяP^*на формулу выше и сокращая обе части равенства наP, получаем

1 = \dfrac{1}{a_1} + \dfrac{1}{a_2} + \ldots + \dfrac{1}{a_n}.

Так это же египетские дроби! "Классическому" варианту задачи соответствует сумма изNдробей, каждая из которых равна1/N, и эта сумма действительно равна1. Будем записывать такие задачи в виде последовательности(N, N, \ldots, N). Дляn=2у нас есть только классическая задача(2,2). Дляn=3помимо классической(3,3,3)можно ввести в рассмотрение также задачи(2,4,4)и(2,3,6). Дляn=4...

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

  1. Докажите, что при каждомnуравнение

    \dfrac{1}{x_1} + \dfrac{1}{x_2} + \ldots + \dfrac{1}{x_n} = 1

    в натуральных числах имеет конечное число решений.

  2. Придумайте алгоритм генерации всех таких решений для заданногоn. Не обязательно, чтобы алгоритм был супер эффективным, но нужно чтобы он находил все решения.

  3. Найдите все решения дляn=4.

Решения пишите в комментариях, в следующей части я напишу свои решения. Как раз решения уравнения дляn=4и будут составлять "бенчмарк" для тестирования последующих улучшений алгоритма решения изначальной классической задачи Винтика и Шпунтика.

Пример кода

Я набросал пример кода для трехзначных чисел на языке Python. Его можно видеть ниже:

def solve_vs( A, B, C ):
	# build graph
	L = []
	for i in range(A):
		for j in range(B):
			for k in range(C):
				L.append( [i, j, k] )
	R = []
	for j in range(B):
		for k in range(C):
			R.append( [-1, j, k] )
	for i in range(A):
		for k in range(C):
			R.append( [i, -1, k] )
	for i in range(A):
		for j in range(B):
			R.append( [i, j, -1] )
	G = []
	for x in L:
		adj = []
		for i in range(len(R)):
			flag = True
			for j in range(3):
				if x[j]!=-1 and R[i][j]!=-1 and x[j]!=R[i][j]:
					flag = False
			if flag: adj.append( i )
		G.append( adj )

	# calc formula
	ans = 0
	k = 0
	last_ans = 0
	for s in range((1<<len(R))-1,-1,-1):
		if k<len(R) and ((s>>k)&1)==0:
			print( "subtotal", k, ans, file=sys.stderr )
			print( "subtotal", k, ans, last_ans/ans, flush=True )
			last_ans = ans
			k += 1
		prod = -1 if (((1<<len(R))-1-s).bit_count()&1) else 1
		for adj in G:
			sum = 0
			for u in adj:
				if ((s>>u)&1):
					sum += 1
			prod *= sum
		ans += prod
	print( "total", ans )

solve_vs( 3, 3, 3 )

Первая половина кода отвечает за построение графа. Сначала мы формируем числа, соответствующие вершинам левой и правой долей. Храним их в виде списков "цифр", звёздочку обозначаем как "-1". Граф хранится в виде списков смежности для вершин левой доли.

Далее вычисляем ответ по формуле. Вsперебирается подмножество вершин правой доли. При этом единичный бит означает, что соответствующую вершину правой доли можно покрывать (а можно не покрывать), а нулевой бит — что данную вершину покрывать нельзя. Количество способов провести ребро для каждой вершины левой долиvвычисляется как количество "разрешенных" вершин правой доли, куда есть ребро изv. Мы перемножаем все эти способы по всем вершинам левой доли и прибавляем к ответу (или вычитаем, в зависимости от четности количества нулей вs).

Как только все комбинации для каких-нибудьkпоследних битовsбудут обработаны — мы выводим промежуточный ответ. Это именно те приближения к финальному ответу, которые обсуждались в разделе про скульптора.

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

Задача

Ответ

Разложение на простые

(3,3,3)

10752

2^9 \cdot 3 \cdot 7

(2,4,4)

23040

2^9 \cdot 3^2 \cdot 5

(2,3,6)

46080

2^{10} \cdot 3^2 \cdot 5

Интересное наблюдение: ответ для(2,4,4)ровно в два раза меньше, чем для(2,3,6). Совпадение или закономерность? Кто знает...

Полные логи программы для каждого случая можно посмотреть под спойлерами:

(3, 3, 3)
subtotal 0 7625597484987 0.0
subtotal 1 5366161193139 1.4210526315789473
subtotal 2 3776187506283 1.4210526315789473
subtotal 3 2657317134051 1.4210526315789473
subtotal 4 1869963909147 1.4210526315789473
subtotal 5 1315900528659 1.4210526315789473
subtotal 6 926004075723 1.4210526315789473
subtotal 7 651632497731 1.4210526315789473
subtotal 8 458556202107 1.4210526315789473
subtotal 9 322687697779 1.4210526315789473
subtotal 10 193593800315 1.6668286755771566
subtotal 11 116144990275 1.6668286755771566
subtotal 12 69680220875 1.6668286755771566
subtotal 13 40148676475 1.735554618304513
subtotal 14 23133052715 1.735554618304513
subtotal 15 13328910811 1.735554618304513
subtotal 16 7218187044 1.8465732087227413
subtotal 17 3908963376 1.8465732087227413
subtotal 18 2116874304 1.8465732087227413
subtotal 19 878041080 2.410905767643582
subtotal 20 339434928 2.586772920434399
subtotal 21 118398672 2.866881209613567
subtotal 22 40756224 2.9050451778849777
subtotal 23 12553776 3.246531083556055
subtotal 24 3287856 3.8182256157203964
subtotal 25 724464 4.53832902670112
subtotal 26 118848 6.095718901453958
total 10752
(2, 4, 4)
subtotal 0 1853020188851841 0.0
subtotal 1 1029455660473245 1.8
subtotal 2 571919811374025 1.8
subtotal 3 317733228541125 1.8
subtotal 4 176518460300625 1.8
subtotal 5 98065811278125 1.8
subtotal 6 54481006265625 1.8
subtotal 7 30267225703125 1.8
subtotal 8 16815125390625 1.8
subtotal 9 9341736328125 1.8
subtotal 10 5189853515625 1.8
subtotal 11 2883251953125 1.8
subtotal 12 1601806640625 1.8
subtotal 13 889892578125 1.8
subtotal 14 494384765625 1.8
subtotal 15 274658203125 1.8
subtotal 16 152587890625 1.8
subtotal 17 90087890625 1.6937669376693767
subtotal 18 53187890625 1.6937669376693767
subtotal 19 31402130625 1.6937669376693767
subtotal 20 18539817921 1.6937669376693767
subtotal 21 9747221346 1.902061855670103
subtotal 22 5124555396 1.902061855670103
subtotal 23 2694210696 1.902061855670103
subtotal 24 1416468496 1.902061855670103
subtotal 25 581258496 2.4368994272730595
subtotal 26 225833712 2.573834042988232
subtotal 27 81758688 2.762198336646498
subtotal 28 27007968 3.027206193372267
subtotal 29 6994368 3.8613879052403304
subtotal 30 1517184 4.610098709187548
subtotal 31 246528 6.154205607476635
total 23040
(2, 3, 6)
subtotal 0 150094635296999121 0.0
subtotal 1 83385908498332845 1.8
subtotal 2 46325504721296025 1.8
subtotal 3 25736391511831125 1.8
subtotal 4 14297995284350625 1.8
subtotal 5 7943330713528125 1.8
subtotal 6 4412961507515625 1.8
subtotal 7 2451645281953125 1.8
subtotal 8 1362025156640625 1.8
subtotal 9 756680642578125 1.8
subtotal 10 420378134765625 1.8
subtotal 11 233543408203125 1.8
subtotal 12 129746337890625 1.8
subtotal 13 72081298828125 1.8
subtotal 14 40045166015625 1.8
subtotal 15 22247314453125 1.8
subtotal 16 12359619140625 1.8
subtotal 17 6866455078125 1.8
subtotal 18 3814697265625 1.8
subtotal 19 1861572265625 2.0491803278688523
subtotal 20 908447265625 2.0491803278688523
subtotal 21 443322265625 2.0491803278688523
subtotal 22 216341265625 2.0491803278688523
subtotal 23 105574537625 2.0491803278688523
subtotal 24 51520374361 2.0491803278688523
subtotal 25 20270311224 2.5416666666666665
subtotal 26 7975204416 2.5416666666666665
subtotal 27 3137785344 2.5416666666666665
subtotal 28 1234538496 2.5416666666666665
subtotal 29 485720064 2.5416666666666665
subtotal 30 191102976 2.5416666666666665
subtotal 31 77723072 2.4587676616796617
subtotal 32 28343168 2.7422154079600416
subtotal 33 8951040 3.1664664664664666
subtotal 34 2311680 3.872093023255814
subtotal 35 437760 5.280701754385965
total 46080

Можно видеть, как количество способов при каждом удвоении числа слагаемых формулы уменьшается. Числа в конце каждой строки означают отношение числа способов в предыдущей строке к числу способов в текущей. Эти отношения постепенно увеличиваются, то есть число способов уменьшается с характерным "свистом". То есть, с ускорением. Это все мы будем использовать для эвристической оценки ответа для задачи(4,4,4,4).

Заключение

В этой части мы познакомились с формулой включений-исключений. Она даёт алгоритм решения задачи Винтика и Шпунтика за времяO^*(2^{N^N}), что значительно быстрее наивного перебора заO^*(N^{N^N}), и уж тем более быстрее наивного вычисления перманента матрицы смежности двудольного графа заO^*((N^N)!). Тем не менее, дляN=4мы получаем2^{256}слагаемых в формуле, и вычислить их сумму (если считать "в лоб") за разумное время решительно невозможно. Однако мы рассмотрели самый общий подход, который работает вообще для любого двудольного графа (у которого доли равномощны). У нас же двудольный граф специального вида, и для него вычисление формулы можно ускорить как минимум доO^*(2^{N^{N-2}(N+1)}), что дляN=4дает порядка2^{80}операций. Это гораздо меньше2^{256}, но подробности — в следующей части.

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

Публикации

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