Supongamos que todos los lados d=6 tienen las mismas posibilidades. Generalicemos y encontremos el número esperado de tiradas necesarias hasta que el lado 1 haya aparecido n1 veces, el lado 2 haya aparecido n2 veces, ..., y el lado d haya aparecido nd veces. Debido a que las identidades de los lados no importan (todos ellos tienen las mismas posibilidades), la descripción de este objetivo se puede condensar: supongamos que i0 lados no tienen que aparecer en absoluto, i1 de los lados es necesario que aparezca solo una vez, ... y inn=max(n1,n2,…,nd)
i=(i0,i1,…,in)
e(i)
e(0,0,0,6)i3=6
Una recurrencia fácil está disponible. En el próximo lanzamiento, el lado que aparece corresponde a uno de los : es decir, o no necesitábamos verlo, o necesitábamos verlo una vez, ..., o necesitábamos verlo más veces . es la cantidad de veces que necesitábamos verlo.ijnj
Cuando , no necesitábamos verlo y nada cambia. Esto sucede con probabilidad .j=0i0/d
Cuando entonces necesitábamos ver este lado. Ahora hay un lado menos que necesita verse veces y un lado más que necesita verse veces. Por lo tanto, convierte en e convierte en . Deje que esta operación en los componentes de se designe , para quej j - 1 i j i j - 1 i j - 1 i j + 1 i i ⋅ jj>0jj - 1yojyoj- 1yoj - 1yoj+ 1yoi ⋅j
i ⋅j=( i0 0, ... , yoj - 2, ij - 1+ 1 , yoj- 1 , yoj + 1, ... , yonorte) .
Esto sucede con probabilidad .yoj/ d
Simplemente tenemos que contar esta tirada de dados y usar la recursión para decirnos cuántas tiradas más se esperan. Por las leyes de la expectativa y la probabilidad total,
e ( i ) = 1 + i0 0ree ( i ) + ∑j = 1norteyojree ( i ⋅ j )
(Comprendamos que siempre que , el término correspondiente en la suma es cero).yoj= 0
Si , hemos terminado y . De lo contrario, podemos resolver para , dando la fórmula recursiva deseadae ( i ) = 0 e ( i )yo0 0= de ( i ) = 0e ( i )
e ( i ) = d+ i1e ( i ⋅ 1 ) + ⋯ + inortee(i⋅n)d−i0.(1)
Tenga en cuenta que es el número total de eventos que deseamos ver. La operación reduce esa cantidad en uno para cualquier siempre que , que es siempre el caso. Por lo tanto, esta recursión termina a una profundidad precisa(igual a en la pregunta). Además (como no es difícil de verificar) el número de posibilidades en cada profundidad de recursión en esta pregunta es pequeño (nunca excede de ). En consecuencia, este es un método eficiente, al menos cuando las posibilidades combinatorias no son demasiado numerosas y recordamos los resultados intermedios (de modo que no haya valor de⋅ j j > 0 i j > 0 | yo | 3 ( 6 ) = 18 8 e
|i|=0(i0)+1(i1)+⋯+n(in)
⋅jj>0ij>0|i|3(6)=188mi se calcula más de una vez).
que
e ( 0 , 0 , 0 , 6 ) = 228687860450888369984000000000≈ 32.677.
Eso me pareció terriblemente pequeño, así que realicé una simulación (usando R
). Después de más de tres millones de tiradas de dados, este juego se jugó hasta su finalización más de 100,000 veces, con una longitud promedio de . El error estándar de esa estimación es : la diferencia entre este promedio y el valor teórico es insignificante, lo que confirma la precisión del valor teórico.0,02732,6690,027
La distribución de longitudes puede ser de interés. (Obviamente, debe comenzar a las , el número mínimo de rollos necesarios para recoger los seis lados tres veces cada uno).18 años
# Specify the problem
d <- 6 # Number of faces
k <- 3 # Number of times to see each
N <- 3.26772e6 # Number of rolls
# Simulate many rolls
set.seed(17)
x <- sample(1:d, N, replace=TRUE)
# Use these rolls to play the game repeatedly.
totals <- sapply(1:d, function(i) cumsum(x==i))
n <- 0
base <- rep(0, d)
i.last <- 0
n.list <- list()
for (i in 1:N) {
if (min(totals[i, ] - base) >= k) {
base <- totals[i, ]
n <- n+1
n.list[[n]] <- i - i.last
i.last <- i
}
}
# Summarize the results
sim <- unlist(n.list)
mean(sim)
sd(sim) / sqrt(length(sim))
length(sim)
hist(sim, main="Simulation results", xlab="Number of rolls", freq=FALSE, breaks=0:max(sim))
Implementación
Aunque el cálculo recursivo de es simple, presenta algunos desafíos en algunos entornos informáticos. El principal de ellos es almacenar los valores de medida que se calculan. Esto es esencial, ya que de lo contrario cada valor se calculará (de forma redundante) una cantidad muy grande de veces. Sin embargo, el almacenamiento potencialmente necesario para una matriz indexada por podría ser enorme. Idealmente, solo los valores de que realmente se encuentran durante el cálculo deben almacenarse. Esto requiere un tipo de matriz asociativa.mie ( i )yoyo
Para ilustrar, aquí está el R
código de trabajo . Los comentarios describen la creación de una clase simple "AA" (matriz asociativa) para almacenar resultados intermedios. Los vectores se convierten en cadenas y se usan para indexar en una lista que contendrá todos los valores. La operación se implementa como .yoE
i ⋅j%.%
Estos preliminares permiten que la función recursiva se defina de manera bastante simple de forma paralela a la notación matemática. En particular, la líneami
x <- (d + sum(sapply(1:n, function(i) j[i+1]*e.(j %.% i))))/(d - j[1])
es directamente comparable a la fórmula anterior. Tenga en cuenta que todos los índices se han incrementado en porque comienza a indexar sus matrices en lugar de .( 1 )1R
10 0
El tiempo muestra que toma segundos calcular ; su valor es0,01e(c(0,0,0,6))
32.6771634160506
El error de redondeo de punto flotante acumulado ha destruido los dos últimos dígitos (lo que debería ser en 68
lugar de 06
).
e <- function(i) {
#
# Create a data structure to "memoize" the values.
#
`[[<-.AA` <- function(x, i, value) {
class(x) <- NULL
x[[paste(i, collapse=",")]] <- value
class(x) <- "AA"
x
}
`[[.AA` <- function(x, i) {
class(x) <- NULL
x[[paste(i, collapse=",")]]
}
E <- list()
class(E) <- "AA"
#
# Define the "." operation.
#
`%.%` <- function(i, j) {
i[j+1] <- i[j+1]-1
i[j] <- i[j] + 1
return(i)
}
#
# Define a recursive version of this function.
#
e. <- function(j) {
#
# Detect initial conditions and return initial values.
#
if (min(j) < 0 || sum(j[-1])==0) return(0)
#
# Look up the value (if it has already been computed).
#
x <- E[[j]]
if (!is.null(x)) return(x)
#
# Compute the value (for the first and only time).
#
d <- sum(j)
n <- length(j) - 1
x <- (d + sum(sapply(1:n, function(i) j[i+1]*e.(j %.% i))))/(d - j[1])
#
# Store the value for later re-use.
#
E[[j]] <<- x
return(x)
}
#
# Do the calculation.
#
e.(i)
}
e(c(0,0,0,6))
Finalmente, aquí está la implementación original de Mathematica que produjo la respuesta exacta. La memorización se realiza a través de la e[i_] := e[i] = ...
expresión idiomática , eliminando casi todos los R
preliminares. Sin embargo, internamente, los dos programas están haciendo las mismas cosas de la misma manera.
shift[j_, x_List] /; Length[x] >= j >= 2 := Module[{i = x},
i[[j - 1]] = i[[j - 1]] + 1;
i[[j]] = i[[j]] - 1;
i];
e[i_] := e[i] = With[{i0 = First@i, d = Plus @@ i},
(d + Sum[If[i[[k]] > 0, i[[k]] e[shift[k, i]], 0], {k, 2, Length[i]}])/(d - i0)];
e[{x_, y__}] /; Plus[y] == 0 := e[{x, y}] = 0
e[{0, 0, 0, 6}]
228687860450888369984000000000