Supposons que tous les côtés ont des chances égales. Généralisons et trouvons le nombre attendu de rouleaux nécessaires jusqu'à ce que le côté 1 apparaisse n 1 fois, le côté 2 apparaisse n 2 fois, ..., et le côté d apparaisse n d fois. Parce que les identités des côtés n'ont pas d'importance (elles ont toutes des chances égales), la description de cet objectif peut être condensée: supposons que i 0 côtés ne doivent pas du tout apparaître, i 1 des côtés doivent apparaître juste une fois, ... et i nré= 61n12n2dndi0i1indes côtés doivent apparaître fois. Soit i = ( i 0 , i 1 , … , e ( 0 , 0 , 0 , 6 ) : i 3 =n=max(n1,n2,…,nd) désigner cette situation et écrire e ( i ) pour le nombre attendu de rouleaux. La question demande
i=(i0,i1,…,in)
e(i)
e(0,0,0,6) indique que les six côtés doivent être vus trois fois chacun.
i3=6
Une récurrence facile est disponible. Au rouleau suivant, le côté qui apparaît correspond à l'un des : c'est-à-dire que nous n'avions pas besoin de le voir, ou nous devions le voir une fois, ..., ou nous devions le voir n plus fois. j est le nombre de fois que nous avons eu besoin de le voir.ijnj
Quand , nous n'avions pas besoin de le voir et rien ne change. Cela se produit avec la probabilité i 0 / d .j = 0je0/ d
Lorsque nous devions voir ce côté. Maintenant, il y a un côté de moins qui doit être vu j fois et un autre côté qui doit être vu j - 1 fois. Ainsi, i j devient i j - 1 et i j - 1 devient i j +j > 0jj - 1jejjej- 1jej - 1 . Soit cette opération sur les composantes de i désignée i ⋅ j , de sorte quejej+ 1jei ⋅j
i ⋅j=( i0, … , Jej - 2, jej - 1+ 1 , ij- 1 , jej + 1, … , Jen) .
Cela se produit avec la probabilité .jej/ d
Nous devons simplement compter ce jet de dé et utiliser la récursivité pour nous dire combien de rouleaux de plus sont attendus. Par les lois de l'espérance et de la probabilité totale,
e(i)=1+i0de(i)+∑j=1nijde(i⋅j)
(Comprenons que chaque fois que , le terme correspondant dans la somme est zéro.)ij=0
Si , nous avons terminé et e ( i ) = 0i0=de(i)=0 . Sinon, nous pouvons résoudre pour , en donnant la formule récursive souhaitéee(i)
e(i)=d+i1e(i⋅1)+⋯+ine(i⋅n)d−i0.(1)
Notez que est le nombre total d'événements que nous souhaitons voir. L'opération ⋅ j réduit cette quantité par une pour tout j > 0 fourni i j > 0 , ce qui est toujours le cas. Par conséquent, cette récursivité se termine à une profondeur de précisément | je | (égal à 3 ( 6 ) =
|i|=0(i0)+1(i1)+⋯+n(in)
⋅jj>0ij>0|i| dans la question). De plus (comme cela n'est pas difficile à vérifier) le nombre de possibilités à chaque profondeur de récursivité dans cette question est faible (ne dépassant jamais
8 ). Par conséquent, c'est une méthode efficace, du moins lorsque les possibilités combinatoires ne sont pas trop nombreuses et que nous mémorisons les résultats intermédiaires (de sorte qu'aucune valeur de
e n'est calculée plus d'une fois).
3(6)=188e
Je calcule que
e(0,0,0,6)=228687860450888369984000000000≈32.677.
Cela me semblait terriblement petit, alors j'ai exécuté une simulation (en utilisant R
). Après plus de trois millions de lancers de dés, ce jeu avait été joué jusqu'à son terme plus de 100 000 fois, avec une longueur moyenne de . L'erreur type de cette estimation est32.669 : la différence entre cette moyenne et la valeur théorique est insignifiante, confirmant l'exactitude de la valeur théorique.0.027
La distribution des longueurs peut être intéressante. (Évidemment, il doit commencer à , le nombre minimum de rouleaux nécessaires pour collecter les six côtés trois fois chacun.)18
# 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))
la mise en oeuvre
Bien que le calcul récursif de soit simple, il présente certains défis dans certains environnements informatiques. Le plus important d'entre eux est le stockage des valeurs de e ( i ) lors de leur calcul. Ceci est essentiel, sinon chaque valeur sera (redondante) calculée un très grand nombre de fois. Cependant, le stockage potentiellement nécessaire pour un tableau indexé par i pourrait être énorme. Idéalement, seules les valeurs de i réellement rencontrées lors du calcul devraient être stockées. Cela nécessite une sorte de tableau associatif.ee(i)ii
Pour illustrer, voici le R
code de travail . Les commentaires décrivent la création d'une simple classe "AA" (tableau associatif) pour stocker les résultats intermédiaires. Les vecteurs sont convertis en chaînes et ceux-ci sont utilisés pour indexer dans une liste qui contiendra toutes les valeurs. L' i ⋅ jiE
i⋅j opération est implémentée comme %.%
.
Ces préliminaires permettent la fonction récursive e de définir assez simplement d'une manière parallèle à la notation mathématique. En particulier, la ligne
x <- (d + sum(sapply(1:n, function(i) j[i+1]*e.(j %.% i))))/(d - j[1])
est directement comparable à la formule ci-dessus. Notez que tous les index ont été augmentés de 1 car commence à indexer ses tableaux à 1 plutôt qu'à 0(1)1R
10 .
Le timing montre qu'il faut seconde pour calculer0.01e(c(0,0,0,6))
; sa valeur est
32,6771634160506
L'erreur d'arrondi à virgule flottante accumulée a détruit les deux derniers chiffres (qui devraient être 68
plutôt que 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))
Enfin, voici l' implémentation originale de Mathematica qui a produit la réponse exacte. La mémorisation est réalisée via l' e[i_] := e[i] = ...
expression idiomatique , éliminant presque tous les R
préliminaires. En interne, cependant, les deux programmes font les mêmes choses de la même manière.
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