Roulez pour voir tous les côtés!

10

Disons que vous avez un dé à 20 faces. Vous commencez à lancer ce dé et vous devez le lancer quelques dizaines de fois avant de finalement lancer les 20 valeurs. Vous vous demandez combien de rouleaux ai-je besoin avant d'avoir 50% de chances de voir les 20 valeurs? Et combien de rouleaux de ndé recto dois-je lancer avant de lancer tous les ncôtés?

Après quelques recherches, vous découvrez qu'il existe une formule pour calculer la probabilité de rouler toutes les nvaleurs après les rrouleaux.

P(r, n) = n! * S(r, n) / n**r

S(a, b)dénote les nombres de Stirling du deuxième type , le nombre de façons de partitionner un ensemble de n objets (chaque rouleau) en k sous-ensembles non vides (chaque côté).

Vous trouverez également la séquence OEIS , que nous appellerons R(n), qui correspond à la plus petite rP(r, n)est d'au moins 50%. Le défi consiste à calculer le nterme de cette séquence le plus rapidement possible.

Le défi

  • Étant donné un n, trouvez le plus petit rP(r, n)est supérieur ou égal à 0.5ou 50%.
  • Votre code devrait théoriquement gérer tout entier non négatif nen entrée, mais nous ne testerons votre code que dans la plage de 1 <= n <= 1000000.
  • Pour la notation, nous allons prendre le temps total nécessaire pour faire fonctionner R(n)sur les intrants 1par 10000.
  • Nous vérifierons si vos solutions sont correctes en exécutant notre version de R(n)sur votre sortie pour voir si P(your_output, n) >= 0.5et P(your_output - 1, n) < 0.5, c'est-à-dire que votre sortie est réellement la plus petite rpour une donnée n.
  • Vous pouvez utiliser n'importe quelle définition pour S(a, b)dans votre solution. Wikipedia a plusieurs définitions qui peuvent être utiles ici.
  • Vous pouvez utiliser des fonctions intégrées dans vos solutions, y compris celles qui calculent S(a, b)ou même celles qui calculent P(r, n)directement.
  • Vous pouvez coder en dur jusqu'à 1000 valeurs R(n)et un million de nombres de Stirling, mais aucune de ces limites n'est stricte et peut être modifiée si vous pouvez faire un argument convaincant pour les augmenter ou les diminuer.
  • Vous n'avez pas besoin de vérifier tous les possibles rentre net ceux que rnous recherchons, mais vous devez trouver le plus petit ret pas n'importe rP(r, n) >= 0.5.
  • Votre programme doit utiliser un langage librement exécutable sur Windows 10.

Les spécifications de l'ordinateur qui testera vos solutions sont i7 4790k, 8 GB RAM. Merci à @DJMcMayhem d' avoir fourni son ordinateur pour les tests. N'hésitez pas à ajouter votre propre timing non officiel pour référence, mais le timing officiel sera fourni plus tard une fois que DJ pourra le tester.

Cas de test

n       R(n)
1       1
2       2
3       5
4       7
5       10
6       13
20      67       # our 20-sided die
52      225      # how many cards from a huge uniformly random pile until we get a full deck
100     497
366     2294     # number of people for to get 366 distinct birthdays
1000    7274
2000    15934
5000    44418
10000   95768
100000  1187943
1000000 14182022

Faites-moi savoir si vous avez des questions ou des suggestions. Bonne chance et bonne optimisation!

Sherlock9
la source
1
@JonathanAllan savait que j'aurais dû choisir un libellé différent. Merci pour l'information.
Sherlock9

Réponses:

7

Python + NumPy, 3,95 secondes

from __future__ import division
import numpy as np

def rolls(n):
    if n == 1:
        return 1
    r = n * (np.log(n) - np.log(np.log(2)))
    x = np.log1p(np.arange(n) / -n)
    cx = x.cumsum()
    y = cx[:-1] + cx[-2::-1] - cx[-1]
    while True:
        r0 = np.round(r)
        z = np.exp(y + r0 * x[1:])
        z[::2] *= -1
        r = r0 - (z.sum() + 0.5) / z.dot(x[1:])
        if abs(r - r0) < 0.75:
            return np.ceil(r).astype(int)

for n in [1, 2, 3, 4, 5, 6, 20, 52, 100, 366, 1000, 2000, 5000, 10000, 100000, 1000000]:
    print('R({}) = {}'.format(n, rolls(n)))

import timeit
print('Benchmark: {:.2f}s'.format(timeit.timeit(lambda: sum(map(rolls, range(1, 10001))), number=1)))

Essayez-le en ligne!

Comment ça fonctionne

Celui-ci utilise la série de formes fermées pour P ( r , n ) et sa dérivée par rapport à r , réarrangée pour la stabilité numérique et la vectorisation, pour effectuer une recherche de méthode de Newton pour r telle que P ( r , n ) = 0,5, arrondi r à un entier avant chaque étape, jusqu'à ce que l'étape déplace r de moins de 3/4. Avec une bonne estimation initiale, cela ne prend généralement qu'une ou deux itérations.

x i = log (1 - i / n ) = log (( n - i ) / n )
cx i = log ( n ! / (( n - i - 1)! ⋅ n i + 1 )
y i = cx i + cx n - i - 2 - cx n - 1 = log binom ( n , i + 1)
z i = (-1) i + 1 ⋅ binom ( n ,i + 1) ⋅ (( n - i - 1) / n ) r
1 + ∑ z i = n! ⋅ S ( r , n ) / n r = P ( r , n )
z ix i + 1 = (-1) i + 1 ⋅ binom ( n , i + 1) ⋅ (( n - i - 1) / n ) r log (( n - i - 1) / n)
z ix i + 1 = d / d r P ( r , n )

Anders Kaseorg
la source
1
Excellent travail sur toute la réponse! D'abord, j'aurais dû réaliser que 0.366512c'était logquelque chose d'il y a longtemps. Va utiliser -log(log(2)dans ma prochaine itération. Deuxièmement, l'idée d'utiliser la méthode de Newton est également très intelligente et je suis heureux de voir que cela fonctionne si bien. Troisièmement, je vais presque certainement voler exp(log(binom(n, i+1)) + r * log((n-i-1)/n)): P Bravo pour une excellente réponse! : D
Sherlock9
1
J'ai ajouté le timing officiel! Belle réponse BTW :)
James
2
Je suis vraiment confus. J'ai changé l' numpyimportation en from numpy import *et pour une raison quelconque, le temps est passé à 0 ... Essayez-le en ligne ?
notjagan
@notjagan cache hit peut-être?
NoOneIsHere
1
Je voudrais m'excuser pour plusieurs choses: 1) mon plagiat de votre réponse lorsque j'ai essayé de trouver des améliorations; 2) ne pas l'admettre correctement et juste essayer de corriger ma réponse; 3) que ces excuses ont pris autant de temps. J'étais tellement mortifié qu'au début, j'ai simplement abandonné ce défi. Dans une petite tentative de réparation, je suppose qu'il est juste de vous dire que ma principale amélioration de cette réponse passait de la méthode de Newton à l'incrémentation r, car votre approximation initiale est déjà assez bonne. Au plaisir de vous revoir au PPCG, et désolé pour tout.
Sherlock9