[Ceci est une question partenaire pour calculer une probabilité exactement ]
Cette tâche consiste à écrire du code pour calculer une probabilité exactement et rapidement . La sortie doit être une probabilité précise écrite sous forme de fraction dans sa forme la plus réduite. C'est-à-dire qu'il ne devrait jamais sortir 4/8
mais plutôt 1/2
.
Pour un entier positif n
, considérez une chaîne uniformément aléatoire de 1 et -1 de longueur n
et appelez-la A. Maintenant, concaténez à A
sa première valeur. C'est A[1] = A[n+1]
si l'indexation à partir de 1. a A
maintenant une longueur n+1
. Considérons maintenant également une deuxième chaîne aléatoire de longueur n
dont les premières n
valeurs sont -1, 0 ou 1 avec probabilité 1 / 4,1 / 2, 1/4 chacune et appelons-la B.
Considérons maintenant le produit intérieur de A[1,...,n]
et B
et le produit intérieur de A[2,...,n+1]
et B
.
Par exemple, réfléchissez n=3
. Les valeurs possibles pour A
et B
pourraient être A = [-1,1,1,-1]
et B=[0,1,-1]
. Dans ce cas, les deux produits intérieurs sont 0
et 2
.
Votre code doit générer la probabilité que les deux produits internes soient nuls.
En copiant le tableau produit par Martin Büttner, nous avons les exemples de résultats suivants.
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
Langues et bibliothèques
Vous pouvez utiliser n'importe quelle langue et bibliothèque librement disponibles. Je dois être en mesure d'exécuter votre code, veuillez donc inclure une explication complète sur la façon d'exécuter / compiler votre code sous Linux si possible.
La tâche
Votre code doit commencer par n=1
et donner la sortie correcte pour chaque n croissant sur une ligne distincte. Il devrait s'arrêter après 10 secondes.
Le score
Le score est simplement le plus élevé n
atteint avant que votre code ne s'arrête après 10 secondes lorsqu'il est exécuté sur mon ordinateur. S'il y a égalité, le vainqueur sera celui qui obtiendra le plus rapidement le score le plus élevé.
Tableau des entrées
n = 64
en Python . Version 1 par Mitch Schwartzn = 106
en Python . Version 11 juin 2015 par Mitch Schwartzn = 151
en C ++ . Port de la réponse de Mitch Schwartz par kirbyfan64sosn = 165
en Python . Version 11 juin 2015 la version "élagage" de Mitch Schwartz avecN_MAX = 165
.n = 945
en Python par Min_25 en utilisant une formule exacte. Incroyable!n = 1228
en Python par Mitch Schwartz en utilisant une autre formule exacte (basée sur la réponse précédente de Min_25).n = 2761
en Python par Mitch Schwartz en utilisant une implémentation plus rapide de la même formule exacte.n = 3250
en Python en utilisant Pypy par Mitch Schwartz en utilisant la même implémentation. Ce score doitpypy MitchSchwartz-faster.py |tail
éviter les frais généraux de défilement de la console.
la source
Réponses:
Python
Une formule sous forme fermée de
p(n)
estUne fonction de génération exponentielle de
p(n)
estoù
I_0(x)
est la fonction de Bessel modifiée du premier type.Edit on 2015-06-11:
- mise à jour du code Python.
Modifier le 13/06/2015:
- ajout d'une preuve de la formule ci-dessus.
- fixe le
time_limit
.- ajout d'un code PARI / GP.
Python
PARI / GP
Preuve:
ce problème est similaire à un problème de marche aléatoire en 2 dimensions (restreint).
Si
A[i] = A[i+1]
, nous pouvons passer de(x, y)
la(x+1, y+1)
[1 voie],(x, y)
[2] ou moyens(x-1, y-1)
[1 voie].Si
A[i] != A[i+1]
, nous pouvons passer de(x, y)
la(x-1, y+1)
[1 voie],(x, y)
[2] ou moyens(x+1, y-1)
[1 voie].Laissez
a(n, m) = [x^m]((x+1)^n + (x-1)^n)
,b(n) = [x^n](1+x)^{2n}
etc(n)
le nombre de façons de passer de(0, 0)
la(0, 0)
avecn
étapes.Alors,
c(n) = \sum_{i=0}^n a(n, i) * b(i) * b(n-i).
Depuis
p(n) = c(n) / 8^n
, nous pouvons obtenir la formule sous forme fermée ci-dessus.la source
Python
Remarque: Félicitations à Min_25 pour avoir trouvé une solution sous forme fermée!
Merci pour le problème intéressant! Cela peut être résolu en utilisant DP, bien que je ne me sente pas très motivé actuellement pour optimiser la vitesse pour obtenir un score plus élevé. Ça pourrait être bien pour le golf.
Le code a atteint
N=39
en 10 secondes sur cet ancien ordinateur portable exécutant Python 2.7.5.Pour tuple
(a,b,s,t)
:a
est le premier élément deA
,b
est le dernier élément deB
,s
est le produit interne deA[:-1]
etB
, ett
est le produit interne deA[1:-1]
etB[:-1]
, en utilisant la notation de tranche Python. Mon code ne stocke pas les tableauxA
ouB
n'importe où, donc j'utilise ces lettres pour faire référence aux éléments suivants à ajouter àA
etB
, respectivement. Ce choix de nom de variable rend l'explication un peu gênante, mais permet une belle apparenceA*b+a*B
dans le code lui-même. Notez que l'élément ajoutéA
est l'avant-dernier, car le dernier élément est toujours le même que le premier. J'ai utilisé l'astuce de Martin Büttner consistant à inclure0
deux foisB
candidats afin d'obtenir la distribution de probabilité appropriée. Le dictionnaireX
(qui est nomméY
pourN+1
) garde la trace du nombre de tous les tableaux possibles en fonction de la valeur du tuple. Les variablesn
etd
représentent le numérateur et le dénominateur, c'est pourquoi j'ai renommé l'n
énoncé du problème commeN
.L'élément clé de la logique est que vous pouvez mettre à jour à partir
N
de l'N+1
utilisation des seules valeurs du tuple. Les deux produits intérieurs spécifiés dans la question sont donnés pars+A*B
ett+A*b+a*B
. C'est clair si vous examinez un peu les définitions; notez que[A,a]
et[b,B]
sont les deux derniers éléments des tableauxA
etB
respectivement.Notez que
s
ett
sont petits et bornés selonN
, et pour une implémentation rapide dans un langage rapide, nous pourrions éviter les dictionnaires en faveur des tableaux.Il peut être possible de profiter de la symétrie en considérant des valeurs ne différant que par le signe; Je n'ai pas examiné cela.
Remarque 1 : La taille du dictionnaire augmente de façon quadratique
N
, où taille signifie le nombre de paires clé-valeur.Remarque 2 : Si nous définissons une limite supérieure
N
, nous pouvons élaguer les tuples pour lesquelsN_MAX - N <= |s|
et de même pourt
. Cela pourrait être fait en spécifiant un état absorbant, ou implicitement avec une variable pour contenir le nombre d'états élagués (qui devraient être multipliés par 8 à chaque itération).Mise à jour : Cette version est plus rapide:
Optimisations mises en œuvre:
main()
- l'accès aux variables locales est plus rapide que globalN=1
explicitement pour éviter la vérification(1,-1) if N else [a]
(qui impose que le premier élément du tuple soit cohérent, lors de l'ajout d'éléments àA
partir de la liste vide)c
pour ajouter un0
àB
au lieu de faire ces opérations deux fois8^N
ainsi nous n'avons pas besoin de le suivreA
as1
et diviser le dénominateur par2
, puisque les paires valides(A,B)
avecA[1]=1
et celles avecA[1]=-1
peuvent être mises en correspondance biunivoque par négationA
. De même, nous pouvons fixer le premier élément deB
comme non négatif.N_MAX
pour voir quel score il peut obtenir sur votre machine. Il pourrait être réécrit pour trouverN_MAX
automatiquement un approprié par recherche binaire, mais semble inutile? Remarque: Nous n'avons pas besoin de vérifier l'état de l'élagage avant d'atteindreN_MAX / 2
, donc nous pouvons obtenir un peu d'accélération en itérant en deux phases, mais j'ai décidé de ne pas le faire pour la simplicité et la propreté du code.la source
N=57
pour la première version etN=75
pour la seconde.Python
En utilisant l'idée de marche aléatoire de Min_25, j'ai pu arriver à une formule différente:
Voici une implémentation Python basée sur Min_25:
Explication / preuve:
D'abord, nous résolvons un problème de comptage connexe, où nous le permettons
A[n+1] = -A[1]
; c'est-à-dire que l'élément supplémentaire concaténé enA
peut être1
ou-1
indépendamment du premier élément. Nous n'avons donc pas besoin de garder une trace du nombre de foisA[i] = A[i+1]
. Nous avons la marche aléatoire suivante:De
(x,y)
nous pouvons passer à(x+1,y+1)
[1 voie],(x+1,y-1)
[1 voie],(x-1,y+1)
[1 voie],(x-1,y-1)
[1 voie],(x,y)
[4 voies]où
x
ety
pour les deux produits scalaires, et nous comptons le nombre de façons de passer de(0,0)
à(0,0)
parn
étapes. Ce nombre serait ensuite multiplié par2
pour tenir compte du fait que celaA
peut commencer par1
ou-1
.Nous nous référons à rester à
(x,y)
un mouvement zéro .Nous itérons sur le nombre de mouvements non nuls
i
, qui doit être égal pour revenir(0,0)
. Les mouvements horizontaux et verticaux constituent deux marches aléatoires unidimensionnelles indépendantes, qui peuvent être comptées parC(i,i/2)^2
, oùC(n,k)
est le coefficient binomial. (Pour une marche avec desk
marches à gauche et desk
marches à droite, il existe desC(2k,k)
façons de choisir l'ordre des marches.) De plus, il existe desC(n,i)
façons de placer les mouvements et des4^(n-i)
façons de choisir les mouvements zéro. On obtient donc:Maintenant, nous devons revenir au problème d'origine. Définissez une paire autorisée
(A,B)
à convertir si elleB
contient un zéro. Définissez une paire(A,B)
comme étant presque admissible siA[n+1] = -A[1]
et les deux produits scalaires sont tous deux nuls.Lemme: Pour une donnée
n
, les paires presque autorisées sont en correspondance biunivoque avec les paires convertibles.Nous pouvons (de façon réversible) convertir une paire convertible
(A,B)
en une paire presque admissible(A',B')
en niantA[m+1:]
etB[m+1:]
, oùm
est l'indice du dernier zéroB
. La vérification est simple: si le dernier élément deB
est nul, nous n'avons rien à faire. Sinon, lorsque nous nions le dernier élément deA
, nous pouvons nier le dernier élément deB
afin de conserver le dernier terme du produit scalaire décalé. Mais cela annule la dernière valeur du produit scalaire non décalé, nous corrigeons donc cela en annulant l'avant-dernier élément deA
. Mais cela annule l'avant-dernière valeur du produit déplacé, nous annulons donc l'avant-dernier élément deB
. Et ainsi de suite, jusqu'à atteindre un élément zéroB
.Maintenant, nous devons simplement montrer qu'il n'y a pas de paires presque autorisées pour lesquelles
B
ne contient pas de zéro. Pour qu'un produit scalaire soit égal à zéro, nous devons avoir un nombre1
et des-1
termes égaux à annuler. Chaque-1
terme est composé de(1,-1)
ou(-1,1)
. Ainsi, la parité du nombre de ceux-1
qui se produisent est fixée en fonction den
. Si les premier et dernier éléments deA
ont des signes différents, nous changeons la parité, c'est donc impossible.Nous obtenons donc
qui donne la formule ci-dessus (réindexation avec
i' = i/2
).Mise à jour: voici une version plus rapide utilisant la même formule:
Optimisations mises en œuvre:
p(n)
C(n,k)
aveck <= n/2
la source
p(n)
n'a pas besoin d'être une fonction par morceaux. En général, sif(n) == {g(n) : n is odd; h(n) : n is even}
alors vous pouvez écriref(n) == (n-2*floor(n/2))*g(n) + ((n+1)-2*(floor((n+1)/2)))*h(n)
ou utiliser à lan mod 2
place de(n-2*floor(n/2))
. Voir iciExplication de la formule de Min_25
Min_25 a publié une excellente preuve, mais il a fallu un certain temps pour la suivre. C'est un peu d'explication à remplir entre les lignes.
a (n, m) représente le nombre de façons de choisir A telles que A [i] = A [i + 1] m fois. La formule pour a (n, m) est équivalente à a (n, m) = {2 * (n choisissez m) pour nm pair; 0 pour nm impair.} Une seule parité est autorisée car A [i]! = A [i + 1] doit se produire un nombre pair de fois pour que A [0] = A [n]. Le facteur 2 est dû au choix initial A [0] = 1 ou A [0] = -1.
Une fois que le nombre de (A [i]! = A [i + 1]) est fixé à q (nommé i dans la formule c (n)), il se sépare en deux marches aléatoires 1D de longueur q et nq. b (m) est le nombre de façons de faire une marche aléatoire unidimensionnelle de m pas qui se termine au même endroit qu'il a commencé, et a 25% de chances de se déplacer vers la gauche, 50% de chances de rester immobile et 25% de chances de se déplaçant à droite. Une façon plus évidente de déclarer la fonction de génération est [x ^ m] (1 + 2x + x ^ 2) ^ n, où 1, 2x et x ^ 2 représentent respectivement gauche, pas de mouvement et droite. Mais alors 1 + 2x + x ^ 2 = (x + 1) ^ 2.
la source
C ++
Juste un portage de la (excellente) réponse Python de Mitch Schwartz. La principale différence est que je
2
représentais-1
laa
variable et que je faisais quelque chose de similaire pourb
, ce qui me permettait d'utiliser un tableau. En utilisant Intel C ++ avec-O3
, j'ai comprisN=141
! Ma première version a euN=140
.Cela utilise Boost. J'ai essayé une version parallèle mais j'ai rencontré des problèmes.
la source
g++ -O3 kirbyfan64sos.cpp -o kirbyfan64sos -lboost_system -lboost_timer -lboost_chrono -lrt -lgmp
être compilé. (Merci à aditsu.)