Сосчитать деревья


11

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

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

Чтобы увидеть, как выглядят все отдельные деревья размером от 1 до 6, посмотрите здесь .

Серия, которую вы пытаетесь вывести, - это A000055 в OEIS.

Ограничение : Ваше решение должно занять в диапазоне минут или меньше для запуска на входе 6. Это не предназначено для устранения алгоритмов экспоненциального времени, но предназначено для устранения алгоритмов с удвоенным экспоненциальным временем, таких как грубое форсирование по всем наборам ребер.

Ввод: любое неотрицательное целое число.

Ввод может быть любым стандартным способом, включая STDIN, параметр командной строки, ввод функции и т. Д.

Выходные данные: количество различных деревьев с таким количеством вершин, сколько входных данных.

Вывод может быть любым стандартным способом, включая STDOUT, возврат функции и т. Д.

Примеры: 0, 1, 2, 3, 4, 5, 6, 7 должен вернуться 1, 1, 1, 1, 2, 3, 6, 11.

Подсчет очков: код гольфа, байтами. Пусть победит самый короткий код!

Стандартные лазейки запрещены.

Ответы:


3

CJam (69 байт)

]qi:X,{1e|,:):N{N\f{1$%!*}W$.*:+}%1$W%.*:+N,/+}/W\+_1,*X=\_W%.*:+-Y/z

Онлайн демо

объяснение

Основная идея заключается в реализации функции генерации, описанной в OEIS. Входные данные являются неприятным особым случаем, но последние изменения, которые я сделал, в итоге привели к для этого случая, поэтому (для абсолютного значения) приводит их в порядок. Это самый странный трюк здесь.01z

.*:+повторяется три раза и выглядит так, как если бы он был извлечен как {.*:+}:F~. Однако это нарушает особый случай , потому что он вообще не выполняет внешний цикл.0


Мы используем вспомогательную функцию генерации для A000081 , члены которой имеют повторение

a[0] = 0
a[1] = 1
For n >= 1, a[n+1] = (sum_{k=1}^n a[n-k+1] * sum_{d|k} d * a[d]) / n

Я уверен, что некоторые языки имеют встроенные модули для обратного преобразования Мёбиуса , но CJam этого не делает; лучший подход я нашел , чтобы построить массив отображения к а затем сделать точечное умножение с использованием . Обратите внимание , что здесь удобно выстроили начиная с индекса 1, потому что мы хотим , чтобы избежать деления на ноль при настройке весов. Также обратите внимание, что если два массива, предоставленные для точечной операции, не имеют одинаковую длину, то значения из более длинного остаются нетронутыми: поэтому мы должны либо взять первые членов либо сделать так, чтобы массив весов поднялся доdkd×a[d]dk % d == 0 ? d : 0a.*akan, Последний кажется короче. Так что это обратное преобразование Мёбиуса дляN\f{1$%!*}W$.*:+

Если мы назовем результат обратного преобразования Мёбиуса M, теперь у нас

a[n+1]=1nk=1na[nk+1]×M[k]

Числитель, очевидно, является термином из свертки, поэтому мы можем обработать его путем обращения либо копии либо а затем поточечного умножения и суммирования. Опять же, наш индекс варьируется от до , и, кроме того, мы хотим объединить индексы, которые составляют , поэтому снова удобно индексировать с 1, а не с 0. Теперь мы учлиaM1nn+1a

 qi:X,{   ,:):N{N\f{1$%!*}W$.*:+}%1$W%.*:+N,/+}/

Точка вспомогательной производящей функции задается разделом формулы A000055:

G.f.: A(x) = 1 + T(x) - T^2(x)/2 + T(x^2)/2,
where T(x) = x + x^2 + 2*x^3 + ... is the g.f. for A000081.

В терминах это означает, что искомый результат равенa

[x=0]+a[x]+12(a[x/2]i=0na[i]×a[ni])

где для с нечетным мы получаем 0. Самый короткий способ, который я нашел для этого, состоит в том, чтобы надуть нули ( ), а затем взять X-й элемент ( ).a[x/2]x1,*X=

Обратите внимание, что сумма является еще одной сверткой, но на этот раз удобно индексировать от 0. Очевидное решение есть 0\+, но здесь есть небольшая оптимизация. Поскольку , два члена свертки гарантированно равны нулю. Мы могли бы использовать это как возможность индексировать от 1, но особый случай становится безобразным. Если мы вместо того, чтобы делать , свертка дает нам и после вычитания и деления на мы обработали термин вне суммы, а также.X = 0 - 2 a [ x ] + n i = 0 a [ i ] × a [ n - i ] 2 a [ x ]a[0]=0X=0W\+2a[x]+i=0na[i]×a[ni]2a[x]

Итак, мы объяснили

 qi:X,{   ,:):N{N\f{1$%!*}W$.*:+}%1$W%.*:+N,/+}/W\+_1,*X=\_W%.*:+-Y/

Остальные детали относятся к особому случаю. Первоначально я следовал за повторением более строго, начиная с 1]и повторяя с сN=1

1]qi:X,1>{ ... }/

В результате, когда , мы вычисляем как : на один член больше, чем нам нужно. Инфляция и свертка приводят к результату . (Возможно, лучше, чем мы заслуживаем - это то, что мы должны иметь, потому что мы ничего не сделали с термином ). Таким образом, мы исправим это для трех символов либо как финал, либо используя стандартную технику «отступления» как .a 0 [ x = 0 ]X=0a[-1 1]0[x=0]X!+1e|

Чтобы получить «правильную» длину нам нужно не указывать эту начальную и вместо этого производить ее из основного цикла с . Совершенно просто1 Н = 0a1N=0

]qi:X,{ ... /+}/

очевидно дает деление на ноль. Но если мы попробуем

]qi:X,{1e| ... /+}/

тогда это работает. Мы получаем

             e# Stack: [] 0
1e|          e# Stack: [] 1
,:):N        e# Stack: [] [1]
{            e# We only execute this loop once
  N\f{1$%!*} e#   1 divides 1, so stack: [] [1]
  W$.*       e#   Remember: if the two arrays supplied to the pointwise operation
             e#   are not the same length then the values from the longer one are
             e#   left untouched. Stack: [] [1]
  :+         e#   Fold over a singleton. Stack: [] 1
}%           e# And that was a map, so stack: [] [1]
1$W%.*:+     e# Another [1] [] .*:+, giving the same result: 1
N,/          e# 1 / 1 = 1
+            e# And we append 1 to a giving [1]

который производит именно то значение, которое нам требуется.

Теперь, если этот основной цикл никогда не выполняется, поэтому после того, как мы введем в нижней части, мы получим . В итоге мы вычисляем , что не правильно. Но в то время как исправление от до заняло три символа, исправление от до принимает только один: дает абсолютное значение.- 1 ( - 1 - 1X=01[-1]01-11(112(1×1))=10111z


1

Pyth, 35 байтов

l{m`SmSSMcXdUQk2.pQusm+L,dhHGhHtQ]Y

Демонстрация.

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

Этот код генерирует деревья: usm+L,dhHGhHtQ]Y. Деревья представлены в виде составного списка ребер, что-то вроде этого:

[0, 1, 0, 2, 2, 3, 1, 4]

Каждое число обозначает вершину, а каждые два числа - ребро. Он создается путем многократного добавления ребра между каждой возможной вершиной, которая уже существует, и вновь созданной, и добавлением этого к каждому возможному дереву из предыдущего шага. Это генерирует все возможные деревья, поскольку все деревья могут быть сгенерированы путем многократного добавления одной вершины и ребра от нее к существующему дереву. Однако изоморфные деревья будут созданы.

Далее для каждого дерева мы выполняем все возможные перемаркировки. Это делается путем отображения всех возможных перестановок вершин ( m ... .pQ), а затем путем перевода дерева из стандартного порядка в этот порядок с помощью XdUQk. dэто дерево, kэто перестановка.

Затем мы разделяем ребра на отдельные списки c ... 2, сортируем вершины внутри каждого ребра SM, сортируем ребра внутри дерева S, давая каноническое представление каждого дерева. Эти два шага являются кодом mSSMcXdUQk2.pQ.

Теперь у нас есть список списков, составленных из всех возможных перемаркировок каждого дерева. Мы сортируем эти списки с помощью S. Любые два изоморфных дерева должны иметь возможность быть перемаркированы в группу деревьев. Используя этот факт, мы преобразуем каждый список в строку с `, затем формируем набор этих списков с помощью {и выводим его длину с помощью l.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.