Conte as árvores


11

Uma árvore é um gráfico conectado, não direcionado, sem ciclos. Sua tarefa é contar quantas árvores distintas existem com um determinado número de vértices.

Duas árvores são consideradas distintas se não forem isomórficas . Dois gráficos são isomórficos se seus respectivos vértices puderem ser emparelhados de tal maneira que exista uma aresta entre dois vértices em um gráfico se e somente se houver uma aresta entre os vértices emparelhados com esses vértices no outro gráfico. Para uma descrição mais completa, consulte o link acima.

Para ver como são todas as diferentes árvores de tamanhos 1 a 6, dê uma olhada aqui .

A série que você está tentando gerar é A000055 no OEIS.

Restrição : Sua solução deve levar no intervalo de minutos ou menos para executar na entrada 6. Isso não se destina a eliminar algoritmos de tempo exponencial, mas a eliminar algoritmos de tempo duplamente exponenciais, como força bruta em todos os conjuntos de arestas.

Entrada: Qualquer número inteiro não negativo.

A entrada pode ser feita por qualquer meio padrão, incluindo STDIN, parâmetro de linha de comando, entrada de função, etc.

Saída: O número de árvores distintas com tantos vértices quanto a entrada.

A saída pode ser realizada por qualquer meio padrão, incluindo STDOUT, retorno de função, etc.

Exemplos: 0, 1, 2, 3, 4, 5, 6, 7 deve retornar 1, 1, 1, 1, 2, 3, 6, 11.

Pontuação: código de golfe, por bytes. Que ganhe o menor código!

Falhas padrão proibidas.

Respostas:


3

CJam (69 bytes)

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

Demonstração online

Explicação

A idéia básica é implementar a função geradora descrita em OEIS. A entrada é um caso especial desagradável, mas os ajustes finais que fiz acabaram produzindo para esse caso, então o (para valor absoluto) o arruma. Esse é o truque mais estranho aqui.01z

.*:+é repetido três vezes e parece que poderia salvar um byte se extraído como {.*:+}:F~. No entanto, isso rompe com o caso especial , porque não executa o loop externo.0


Usamos a função de geração auxiliar para A000081 , cujos termos têm a recorrência

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

Eu tenho certeza que algumas linguagens possuem built-ins para a transformação inversa de Möbius , mas o CJam não; a melhor abordagem que eu encontrei é para construir um conjunto de mapeamento para e, em seguida, fazer uma multiplicação pontual com utilização . Observe que aqui é conveniente criar partida no índice 1, porque queremos evitar a divisão por zero ao configurar os pesos. Observe também que se as duas matrizes fornecidas para a operação apontada não tiverem o mesmo comprimento, os valores da mais longa permanecerão intocados: portanto, temos que pegar os primeiros termos de ou fazer com que a matriz de pesos suba paradkd×a[d]dk % d == 0 ? d : 0a.*akan. O último parece mais curto. Portanto, essa transformação inversa de Möbius é responsável porN\f{1$%!*}W$.*:+

Se chamarmos o resultado da transformação inversa de Möbius M, agora temos

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

Obviamente, o numerador é um termo de uma convolução, para que possamos manipulá-lo revertendo uma cópia de ou e, em seguida, efetuando uma multiplicação e soma pontual. Novamente, nosso índice varia de a e, além disso, queremos emparelhar índices que somam , por isso é conveniente indexar de 1 em vez de 0. Agora, respondemos poraM1nn+1a

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

O ponto da função auxiliar de geração é dado pela seção de fórmula de 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.

Em termos de , isso significa que a saída que buscamos éa

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

onde para com ímpar obtemos 0. A maneira mais curta que encontrei para fazer isso é inflar com zeros ( ) e depois pegar o Xº elemento ( ).a[x/2]x1,*X=

Observe que a soma é mais uma convolução, mas desta vez é conveniente indexar a partir de 0. A solução óbvia é 0\+, mas há uma ótima otimização aqui. Como , dois termos da convolução são garantidos como zero. Podemos aproveitar isso como uma oportunidade para indexar a partir de 1, mas o caso especial fica feio. Se o fizermos , a convolução nos dará e após a subtração e divisão por lidamos com o termo a fora da soma também.a[0]=0X=0W\+2a[x]+i=0na[i]×a[ni]2a[x]

Então nós explicamos

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

Os demais detalhes estão relacionados ao caso especial. Originalmente, segui a recorrência mais estritamente começando 1]e iterando de comN=1

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

O resultado quando é que computamos como : um termo a mais do que precisamos. A inflação e a convolução acabam dando um resultado de . (Talvez melhor do que merecemos - é o que deveríamos ter, porque não fizemos nada sobre o termo ). Portanto, corrigimos isso para três caracteres como final ou usando a técnica padrão de "fallback" como .X=0a[-1 1]0[x=0]X!+1e|

Para obter o comprimento "certo" de , precisamos evitar o fornecimento iniciala1N=0

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

obviamente dá divisão por zero. Mas se tentarmos

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

então funciona. Nós temos

             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]

que produz exatamente o valor que exigimos.

X=01[-1](112(1×1))=10111z


1

Pitão, 35 bytes

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

Demonstração.

Este programa pode ser dividido em duas partes: primeiro, geramos todas as árvores possíveis e depois removemos duplicatas.

Este código gera as árvores: usm+L,dhHGhHtQ]Y. As árvores são representadas como uma lista concatenada de arestas, algo como isto:

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

Cada número representa um vértice e a cada dois números é uma aresta. Ele é criado adicionando repetidamente uma aresta entre cada vértice possível que já existe e um que foi criado recentemente e adicionando-o a cada árvore possível da etapa anterior. Isso gera todas as árvores possíveis, uma vez que todas as árvores podem ser geradas adicionando repetidamente um vértice e uma aresta à árvore existente. No entanto, árvores isomórficas serão criadas.

A seguir, para cada árvore, realizamos todas as remarcações possíveis. Isso é feito através do mapeamento de todas as permutações possíveis dos vértices ( m ... .pQ) e, em seguida, transacionando a árvore da ordem padrão para aquela ordem, com XdUQk. dé a árvore, ké a permutação.

Em seguida, separamos as arestas em listas separadas com c ... 2, classificamos os vértices dentro de cada aresta SM, Sseparamos as arestas dentro da árvore , fornecendo uma representação canônica de cada árvore. Essas duas etapas são o código mSSMcXdUQk2.pQ.

Agora, temos uma lista de listas compostas por todas as remarcações possíveis de cada árvore. Classificamos essas listas com S. Quaisquer duas árvores isomórficas devem poder ser rotuladas novamente no grupo de árvores. Usando esse fato, convertemos cada lista em uma string com `, em seguida, formamos o conjunto dessas listas com {e produzimos seu comprimento com l.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.