[Esta é uma pergunta de parceiro para calcular exatamente uma probabilidade ]
Esta tarefa é sobre escrever código para calcular uma probabilidade exata e rapidamente . A saída deve ser uma probabilidade precisa escrita como uma fração em sua forma mais reduzida. Ou seja, nunca deve produzir, 4/8
mas sim 1/2
.
Para um número inteiro positivo n
, considere uma sequência uniformemente aleatória de 1s e -1s de comprimento n
e chame-a de A. Agora concatene A
seu primeiro valor. Ou seja, A[1] = A[n+1]
se a indexação de 1. A
agora tiver comprimento n+1
. Agora considere também uma segunda sequência aleatória de comprimento n
cujos primeiros n
valores sejam -1, 0 ou 1 com probabilidade 1 / 4,1 / 2, 1/4 cada e chame-a de B.
Agora considere o produto interno de A[1,...,n]
e B
e o produto interno de A[2,...,n+1]
e B
.
Por exemplo, considere n=3
. Valores possíveis para A
e B
poderiam ser A = [-1,1,1,-1]
e B=[0,1,-1]
. Nesse caso, os dois produtos internos são 0
e 2
.
Seu código deve gerar a probabilidade de que ambos os produtos internos sejam zero.
Copiando a tabela produzida por Martin Büttner, temos os seguintes resultados de amostra.
n P(n)
1 1/2
2 3/8
3 7/32
4 89/512
5 269/2048
6 903/8192
7 3035/32768
8 169801/2097152
Línguas e bibliotecas
Você pode usar qualquer idioma e bibliotecas disponíveis gratuitamente que desejar. Devo ser capaz de executar seu código, portanto, inclua uma explicação completa de como executar / compilar seu código no Linux, se possível.
A tarefa
Seu código deve começar com n=1
e fornecer a saída correta para cada n crescente em uma linha separada. Deve parar após 10 segundos.
A pontuação
A pontuação é simplesmente a mais alta n
atingida antes que seu código pare após 10 segundos quando executado no meu computador. Se houver um empate, o vencedor será o vencedor mais rápido.
Tabela de entradas
n = 64
em Python . Versão 1 por Mitch Schwartzn = 106
em Python . Versão 11 de junho de 2015 por Mitch Schwartzn = 151
em C ++ . Resposta de Port of Mitch Schwartz por kirbyfan64sosn = 165
em Python . Versão 11 de junho de 2015, a versão "poda" de Mitch Schwartz comN_MAX = 165
.n = 945
em Python por Min_25 usando uma fórmula exata. Surpreendente!n = 1228
em Python, por Mitch Schwartz, usando outra fórmula exata (com base na resposta anterior de Min_25).n = 2761
em Python por Mitch Schwartz usando uma implementação mais rápida da mesma fórmula exata.n = 3250
em Python usando Pypy por Mitch Schwartz usando a mesma implementação. Essa pontuação precisapypy MitchSchwartz-faster.py |tail
evitar a rolagem do console.
fonte
Respostas:
Pitão
Uma fórmula de forma fechada
p(n)
éUma função geradora exponencial de
p(n)
éonde
I_0(x)
é a função de Bessel modificada do primeiro tipo.Edite em 11/06/2015:
- atualizou o código Python.
Editar em 13/06/2015:
- adicionou uma prova da fórmula acima.
- consertou o
time_limit
.- adicionado um código PARI / GP.
Pitão
PARI / GP
Prova:
Esse problema é semelhante a um problema de caminhada aleatória bidimensional (restrita).
Se
A[i] = A[i+1]
, podemos passar de(x, y)
para(x+1, y+1)
[1 via],(x, y)
[2 vias] ou(x-1, y-1)
[1 via].Se
A[i] != A[i+1]
, podemos passar de(x, y)
para(x-1, y+1)
[1 via],(x, y)
[2 vias] ou(x+1, y-1)
[1 via].Vamos
a(n, m) = [x^m]((x+1)^n + (x-1)^n)
,b(n) = [x^n](1+x)^{2n}
ec(n)
ser o número de maneiras de se mover a partir(0, 0)
de(0, 0)
comn
passos.Então,
c(n) = \sum_{i=0}^n a(n, i) * b(i) * b(n-i).
Desde então
p(n) = c(n) / 8^n
, podemos obter a fórmula de formulário fechado acima.fonte
Pitão
Nota: Parabéns a Min_25 por encontrar uma solução em formato fechado!
Obrigado pelo problema interessante! Ele pode ser resolvido usando o DP, embora atualmente não esteja me sentindo muito motivado para otimizar a velocidade para obter uma pontuação mais alta. Poderia ser bom para o golfe.
O código chegou
N=39
em 10 segundos neste laptop antigo executando o Python 2.7.5.Para tupla
(a,b,s,t)
:a
é o primeiro elemento deA
,b
é o último elemento deB
,s
é o produto interno deA[:-1]
eB
, et
é o produto interno deA[1:-1]
eB[:-1]
, usando a notação de fatia do Python. Meu código não armazena as matrizesA
ou emB
qualquer lugar, então eu uso essas letras para me referir aos próximos elementos a serem adicionadosA
eB
, respectivamente. Essa opção de nomeação de variáveis torna a explicação um pouco estranha, mas permite uma boa aparênciaA*b+a*B
no próprio código. Observe que o elemento que está sendo adicionadoA
é o penúltimo, já que o último elemento é sempre o mesmo que o primeiro. Eu usei o truque de Martin Büttner de incluir0
duas vezesB
candidatos para obter a distribuição de probabilidade adequada. O dicionárioX
(que é nomeadoY
paraN+1
) mantém o controle da contagem de todas as matrizes possíveis de acordo com o valor da tupla. As variáveisn
ed
representam numerador e denominador, e foi por isso que renomeei an
declaração do problema comoN
.A parte principal da lógica é que você pode atualizar de
N
paraN+1
usando apenas os valores na tupla. Os dois produtos internos especificados na pergunta são dados pors+A*B
et+A*b+a*B
. Isso fica claro se você examinar um pouco as definições; observe que[A,a]
e[b,B]
são os dois últimos elementos das matrizesA
eB
respectivamente.Observe que
s
et
são pequenos e delimitados de acordo comN
, e para uma rápida implementação em uma linguagem rápida, poderíamos evitar dicionários em favor de matrizes.Pode ser possível tirar proveito da simetria considerando valores que diferem apenas no sinal; Eu não olhei para isso.
Observação 1 : o tamanho do dicionário cresce quadraticamente
N
, em que tamanho significa número de pares de valores-chave.Observação 2 : se definirmos um limite superior
N
, podemos remover tuplas para as quaisN_MAX - N <= |s|
e da mesma forma parat
. Isso pode ser feito especificando um estado de absorção ou implicitamente com uma variável para conter a contagem de estados removidos (que precisariam ser multiplicados por 8 a cada iteração).Atualização : Esta versão é mais rápida:
Otimizações implementadas:
main()
- o acesso variável local é mais rápido que o globalN=1
explicitamente para evitar a verificação(1,-1) if N else [a]
(que impõe que o primeiro elemento da tupla seja consistente ao adicionar elementos aA
partir da lista vazia)c
para adicionar um0
a emB
vez de fazer essas operações duas vezes8^N
então não precisamos acompanhá-loA
as1
e dividir o denominador por2
, uma vez que os pares válidos(A,B)
comA[1]=1
e os comA[1]=-1
podem ser colocados em uma correspondência individual negandoA
. Da mesma forma, podemos corrigir o primeiro elementoB
como não negativo.N_MAX
para ver qual pontuação ele pode obter na sua máquina. Pode ser reescrito para encontrar um apropriadoN_MAX
automaticamente pela pesquisa binária, mas parece desnecessário? Nota: Não precisamos verificar a condição de poda até chegarmos ao redorN_MAX / 2
, para que possamos acelerar um pouco iterando em duas fases, mas decidi não simplificar e limpar o código.fonte
N=57
a primeira versão eN=75
a segunda.Pitão
Usando a ideia de caminhada aleatória de Min_25, consegui chegar a uma fórmula diferente:
Aqui está uma implementação Python baseada em Min_25:
Explicação / prova:
Primeiro, resolvemos um problema de contagem relacionado, onde permitimos
A[n+1] = -A[1]
; isto é, o elemento adicional concatenado paraA
pode ser1
ou-1
independentemente do primeiro elemento. Portanto, não precisamos acompanhar quantas vezesA[i] = A[i+1]
ocorre. Temos o seguinte passeio aleatório:De
(x,y)
podemos mudar para(x+1,y+1)
[1 caminho],(x+1,y-1)
[1 caminho],(x-1,y+1)
[1 caminho],(x-1,y-1)
[1 caminho],(x,y)
[4 caminhos]onde
x
ey
representam os dois produtos do ponto, e contamos o número de maneiras para se deslocar de(0,0)
que(0,0)
emn
etapas. Essa contagem seria então multiplicada2
para levar em conta o fato de queA
pode começar com1
ou-1
.Nós nos referimos a permanecer
(x,y)
como um movimento zero .Nós iteramos sobre o número de movimentos diferentes de zero
i
, que precisam ser uniformes para voltarmos(0,0)
. Os movimentos horizontais e verticais compõem dois passeios aleatórios unidimensionais independentes, que podem ser contadosC(i,i/2)^2
, ondeC(n,k)
é o coeficiente binomial. (Para uma caminhada com osk
passos à esquerda e osk
passos à direita, existemC(2k,k)
maneiras de escolher a ordem dos passos.) Além disso, existemC(n,i)
maneiras de colocar os movimentos e4^(n-i)
maneiras de escolher os movimentos zero. Então temos:Agora, precisamos voltar ao problema original. Defina um par permitido
(A,B)
para ser conversível seB
contiver um zero. Defina um par(A,B)
como quase permitido seA[n+1] = -A[1]
e os dois produtos de ponto forem zero.Lema: Para um dado
n
, os pares quase permitidos estão em correspondência individual com os pares conversíveis.Podemos (reversivelmente) converter um par conversível
(A,B)
em um par quase permitido(A',B')
negandoA[m+1:]
eB[m+1:]
, ondem
está o índice do último zero emB
. A verificação para isso é simples: se o último elemento deB
for zero, não precisamos fazer nada. Caso contrário, quando negamos o último elemento deA
, podemos negar o último elemento deB
para preservar o último termo do produto de ponto alterado. Mas isso nega o último valor do produto de ponto sem deslocamento, portanto, corrigimos isso negando o penúltimo elemento deA
. Mas isso gera o penúltimo valor do produto deslocado, de modo que negamos o penúltimo elemento deB
. E assim por diante, até atingir um elemento zeroB
.Agora, apenas precisamos mostrar que não há pares quase permitidos para os quais
B
não contém zero. Para um produto escalar ser igual a zero, precisamos ter um número igual1
e-1
termos para cancelar. Cada-1
termo é composto por(1,-1)
ou(-1,1)
. Portanto, a paridade do número-1
que ocorre é fixada de acordo comn
. Se o primeiro e o último elementos deA
têm sinais diferentes, mudamos a paridade, então isso é impossível.Então nós temos
que fornece a fórmula acima (reindexação com
i' = i/2
).Atualização: Aqui está uma versão mais rápida usando a mesma fórmula:
Otimizações implementadas:
p(n)
C(n,k)
comk <= n/2
fonte
p(n)
não precisa ser uma função por partes. Em geral, sef(n) == {g(n) : n is odd; h(n) : n is even}
você pode escreverf(n) == (n-2*floor(n/2))*g(n) + ((n+1)-2*(floor((n+1)/2)))*h(n)
ou usar emn mod 2
vez de(n-2*floor(n/2))
. Veja aquiExplicação da fórmula de Min_25
Min_25 postou uma ótima prova, mas demorou um pouco para seguir. Esta é uma explicação para preencher as entrelinhas.
a (n, m) representa o número de maneiras de escolher A, de modo que A [i] = A [i + 1] m vezes. A fórmula para a (n, m) é equivalente a a (n, m) = {2 * (n escolha m) para nm par; 0 para nm ímpar.} Somente uma paridade é permitida porque A [i]! = A [i + 1] deve ocorrer um número par de vezes para que A [0] = A [n]. O fator 2 é devido à escolha inicial A [0] = 1 ou A [0] = -1.
Uma vez que o número de (A [i]! = A [i + 1]) é fixado para ser q (denominado i na fórmula c (n)), ele se separa em duas caminhadas aleatórias 1D de comprimento q e nq. b (m) é o número de maneiras de fazer uma caminhada aleatória unidimensional de m etapas que termina no mesmo local em que começou e tem 25% de chance de se mover para a esquerda, 50% de chance de ficar parado e 25% de chance de movendo para a direita. Uma maneira mais óbvia de indicar a função geradora é [x ^ m] (1 + 2x + x ^ 2) ^ n, onde 1, 2x e x ^ 2 representam esquerda, sem movimento e direita, respectivamente. Mas então 1 + 2x + x ^ 2 = (x + 1) ^ 2.
fonte
C ++
Apenas uma porta da (excelente) resposta em Python de Mitch Schwartz. A principal diferença é que eu costumava
2
representar-1
para aa
variável e fazia algo semelhanteb
, o que me permitia usar uma matriz. Usando o Intel C ++ com-O3
, eu conseguiN=141
! Minha primeira versão chegouN=140
.Isso usa o Boost. Eu tentei uma versão paralela, mas tive alguns problemas.
fonte
g++ -O3 kirbyfan64sos.cpp -o kirbyfan64sos -lboost_system -lboost_timer -lboost_chrono -lrt -lgmp
compilar. (Graças a aditsu.)