Rotoli per vedere tutti i lati!


10

Diciamo che hai un dado a 20 facce. Inizi a lanciare quel dado e devi lanciarlo alcune dozzine di volte prima di lanciare finalmente tutti i 20 valori. Ti chiedi, di quanti tiri ho bisogno prima di avere una probabilità del 50% di vedere tutti i 20 valori? E quanti tiri di ndado a faccia nlaterale devo tirare prima di tirare tutti i lati?

Dopo alcune ricerche, scopri che esiste una formula per calcolare la possibilità di far rotolare tutti i nvalori dopo i rtiri.

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

dove S(a, b)indica numeri di Stirling del secondo tipo , il numero di modi per partizionare una serie di n oggetti (ogni rotolo) in k sottoinsiemi non vuoti (ogni lato).

Troverai anche la sequenza OEIS , che chiameremo R(n), che corrisponde al più piccolo rdove P(r, n)è almeno il 50%. La sfida è calcolare il ntermine di questa sequenza il più velocemente possibile.

La sfida

  • Dato un n, trova il più piccolo r dove P(r, n)è maggiore o uguale 0.5o del 50%.
  • Il tuo codice dovrebbe teoricamente gestire qualsiasi numero intero non negativo ncome input, ma testeremo il tuo codice solo nell'intervallo di 1 <= n <= 1000000.
  • Per il punteggio, ci sarà prendere il tempo totale necessario per l'esecuzione R(n)su ingressi 1attraverso 10000.
  • Controlleremo se le tue soluzioni sono corrette eseguendo la nostra versione del R(n)tuo output per vedere se P(your_output, n) >= 0.5e P(your_output - 1, n) < 0.5, cioè che il tuo output sia effettivamente il più piccolo rper un dato n.
  • È possibile utilizzare qualsiasi definizione per S(a, b)nella propria soluzione. Wikipedia ha diverse definizioni che potrebbero essere utili qui.
  • È possibile utilizzare i componenti integrati nelle soluzioni, inclusi quelli che calcolano S(a, b)o anche quelli che calcolano P(r, n)direttamente.
  • È possibile codificare fino a 1000 valori di R(n)e un milione di numeri Stirling, sebbene nessuno di questi sia un limite rigido e può essere modificato se si può fare un argomento convincente per alzarli o abbassarli.
  • Non è necessario controllare ogni possibile rtra ne quello rche stiamo cercando, ma è necessario trovare il più piccolo re non solo rdove P(r, n) >= 0.5.
  • Il tuo programma deve usare una lingua liberamente eseguibile su Windows 10.

Le specifiche del computer che testerà le tue soluzioni sono i7 4790k, 8 GB RAM. Grazie a @DJMcMayhem per aver fornito il suo computer per i test. Sentiti libero di aggiungere i tuoi tempi non ufficiali per riferimento, ma i tempi ufficiali verranno forniti in seguito una volta che DJ potrà provarli.

Casi 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

Fammi sapere se hai domande o suggerimenti. Buona fortuna e buona ottimizzazione!


1
@JonathanAllan Sapevo che avrei dovuto scegliere una diversa formulazione. Grazie per il testa a testa.
Sherlock9,

Risposte:


7

Python + NumPy, 3,95 secondi

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)))

Provalo online!

Come funziona

Questo utilizza la serie a forma chiusa per P ( r , n ) e la sua derivata rispetto a r , riorganizzata per la stabilità numerica e la vettorializzazione, per fare una ricerca del metodo di Newton per r tale che P ( r , n ) = 0,5, arrotondamento r ad un numero intero prima di ogni passaggio, fino a quando il passo si sposta di r di meno di 3/4. Con una buona ipotesi iniziale, questo di solito richiede solo una o due iterazioni.

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 )


1
Ottimo lavoro su tutta la risposta! Innanzitutto, avrei dovuto rendermi conto che 0.366512era quello logdi qualcosa anni fa. Utilizzerò -log(log(2)nella mia prossima iterazione. In secondo luogo, anche l'idea di utilizzare il metodo di Newton è molto intelligente e sono felice di vedere che funziona così bene. In terzo luogo, ho quasi sicuramente rubato exp(log(binom(n, i+1)) + r * log((n-i-1)/n)): P Kudos su un'ottima risposta! : D
Sherlock9

1
Ho aggiunto i tempi ufficiali! Bella risposta BTW :)
James,

2
Sono veramente confuso. Ho cambiato l' numpyimportazione in from numpy import *e per qualche ragione il tempo è sceso a praticamente 0 ... Provalo online ?
notjagan,

@notjagan cache hit forse?
NoOneIsHere

1
Mi scuso per diverse cose: 1) il mio plagio per la tua risposta quando ho cercato di trovare miglioramenti; 2) non possederlo correttamente e solo provare a risolvere la mia risposta; 3) che questa scusa ha impiegato così tanto tempo. Ero così mortificato che all'inizio ho appena abbandonato questa sfida. In un piccolo tentativo di riparazione, suppongo sia giusto dirti che il mio principale miglioramento su questa risposta stava cambiando dal metodo di Newton a incrementare r, poiché la tua approssimazione iniziale è già abbastanza buona. Spero di rivederti in PPCG ancora una volta, e scusami per tutto.
Sherlock9,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.