Skip to content
Vamshi Jandhyala

Books · The Riddler

Chapter 206

The Riddler Just Had To Go And Reinvent Beer Pong

Riddler Express

Two grandmasters play a World Chess Championship match. The better player wins each game with probability 0.200.20, loses with probability 0.150.15, and draws with probability 0.650.65. Wins count 11 point, draws 0.50.5 each, losses 00. The match is NN games; the first to N/2+0.5N/2 + 0.5 points wins the match outright (ties don’t decide the title here). What are the chances the better player wins a 1212-game match? How many games does the match need to give the better player a 75%75\%, 90%90\%, or 99%99\% chance of winning outright?

The Riddler, FiveThirtyEight, November 16, 2018(original post)

Solution

A game contributes +1+1, 00, or 1-1 to the margin (better player’s score minus opponent’s, in units of half points doubled). The better player wins the match outright when the margin after NN games is strictly positive. The match outcome is the sign of i=1Ngi\sum_{i=1}^{N} g_{i} with gi{+1,0,1}g_{i} \in \{+1, 0, -1\} and probabilities Pr(g=+1)=0.20,Pr(g=+0)=0.65,Pr(g=1)=0.15.\begin{aligned} \Pr(g = +1) &\,=\, 0.20, \\ \Pr(g = \phantom{+}0) &\,=\, 0.65, \\ \Pr(g = -1) &\,=\, 0.15. \end{aligned}

Generating function. Encode each game by the Laurent polynomial f(x)=0.20x+0.65+0.15x1f(x) = 0.20 \, x + 0.65 + 0.15 \, x^{-1}. The match polynomial is f(x)Nf(x)^{N}; the coefficient of xrx^{r} is the probability the margin equals rr. The match-win probability is the sum of coefficients on the positive powers of xx: Pr(better player wins N-game match)  =  r1[xr]f(x)N.\Pr(\text{better player wins } N\text{-game match}) \;=\; \sum_{r \ge 1} [x^{r}] \, f(x)^{N}. This is a clean polynomial computation: expand f(x)Nf(x)^{N} and sum the positive-exponent coefficients.

N=12N = 12. Direct expansion gives Pr(12-game win)    52.0%.\Pr(\text{12-game win}) \;\approx\; \mathbf{52.0\%}. For the thresholds, scan NN upwards and stop at the first NN exceeding each target: N=82 for 75%,N=248 for 90%,N=773 for 99%.N = \mathbf{82} \text{ for } 75\%, \quad N = \mathbf{248} \text{ for } 90\%, \quad N = \mathbf{773} \text{ for } 99\%.

Sanity check. The expected margin per game is 0.200.15=0.050.20 - 0.15 = 0.05, and the variance is 0.20+0.150.052=0.34750.20 + 0.15 - 0.05^{2} = 0.3475. By the central limit theorem, the margin after NN games is approximately Gaussian with mean 0.05N0.05 \, N and standard deviation 0.3475N\sqrt{0.3475 \, N}, and the match-win probability is Φ ⁣(0.05N0.3475N)  =  Φ ⁣(0.0848N).\Phi \!\left( \frac{0.05 \, N}{\sqrt{0.3475 \, N}} \right) \;=\; \Phi \!\left( 0.0848 \sqrt{N} \right). For N=12N = 12 this is Φ(0.294)0.616\Phi(0.294) \approx 0.616 (overshoots a little, as the Gaussian approximation does for a discrete margin near zero). For Φ1(0.75)=0.674\Phi^{-1}(0.75) = 0.674, solving 0.0848N=0.6740.0848 \sqrt{N} = 0.674 gives N63N \approx 63; the true value 8282 exceeds it by about 30%30\%, since for moderate NN the discreteness of the margin (mass at 00, then jumps by 11) and the long tail of draws sit far enough from the asymptotic Gaussian shape that the threshold integer is meaningfully larger.

The computation

Build f(x)Nf(x)^{N} by iterated polynomial multiplication and sum the coefficients on the positive exponents. Use Python integers and rationals to avoid floating-point drift over large NN.

  1. Represent the running polynomial as a dictionary {r:Pr(margin=r)}\{r: \Pr(\text{margin} = r)\}.

  2. Multiply by ff each game: shift mass by +1+1 with weight 0.200.20, leave with 0.650.65, shift by 1-1 with 0.150.15.

  3. Sum positive-exponent mass for the answer.

from collections import defaultdict

def p_win(N):
    coef = {0: 1.0}
    for _ in range(N):
        nxt = defaultdict(float)
        for r, c in coef.items():
            nxt[r + 1] += 0.20 * c
            nxt[r]     += 0.65 * c
            nxt[r - 1] += 0.15 * c
        coef = nxt
    return sum(c for r, c in coef.items() if r > 0)

print(f"N = 12: P(win) = {p_win(12):.4f}")
for target in (0.75, 0.90, 0.99):
    N = 1
    while p_win(N) < target:
        N += 1
    print(f"target {target:.2f}: smallest N = {N}")

The script prints 0.51980.5198 for N=12N = 12 and the thresholds 8282, 248248, 773773, matching the boxed answer.

Riddler Classic

The game has NN cups labelled 11 through NN and an infinite supply of balls each labelled 11 through NN (uniformly random per ball). Each round has two phases. In the throwing phase the player takes balls one at a time from the supply and throws them; each throw lands in a uniformly random cup; the phase ends when every cup contains at least one ball. In the pruning phase the player goes through each cup and removes every ball whose number does not match the cup’s label. The game ends after a round in which no cup is empty after the pruning. How many rounds is the game expected to take? How many throws?

The Riddler, FiveThirtyEight, November 16, 2018(original post)

Solution

After each round, every cup ends either locked (holding at least one ball whose label matches the cup, kept by pruning forever) or open (empty after pruning). Locked cups remain locked since their kept balls satisfy the no-empty-cup termination criterion for free in subsequent rounds. Let KtK_{t} be the number of locked cups at the end of round tt. The game ends at the first tt with Kt=NK_{t} = N.

Throws per round. Suppose round t+1t + 1 starts with Kt=kK_{t} = k locked cups. Locked cups already have a matching ball, so the throwing phase already has them in the "non-empty" state. The phase continues until each of the remaining NkN - k open cups receives at least one ball. Each throw goes uniformly to one of NN cups, so the round’s throw count TT is the cover time for the NkN - k open cups: E[Tk]  =  j=1NkNj.\mathbb{E}[T \mid k] \;=\; \sum_{j=1}^{N - k} \frac{N}{j}. The largest term, when only one open cup remains, contributes NN throws by itself. The sum grows like Nln(Nk)N \ln(N - k) for kNk \ll N.

Locking new cups. Each open cup that receives at least one ball this round has independent probability 1/N1/N that the ball matches the cup label, and the cup might receive several balls (each independently matching with probability 1/N1/N). Let MiM_{i} be the number of balls received by open cup ii in this round; the cup locks iff at least one of those MiM_{i} balls matches, with probability 1(11/N)Mi1 - (1 - 1/N)^{M_{i}} given MiM_{i}.

The exact joint distribution of (M1,,MNk)(M_{1}, \ldots, M_{N - k}) given TT throws is the multinomial restricted to all positive counts. A clean closed form is not at hand, but the round dynamics are easy to simulate, and the asymptotic structure is clear:

  • The number of rounds grows roughly linearly with NN: each round locks an expected Θ(1)\Theta(1) new cups on average among the open ones, since open cup ii receives Θ(lnN)\Theta(\ln N) balls in a typical round when many cups are still open, each matching independently with probability 1/N1/N, so the match probability per round is Θ((lnN)/N)\Theta((\ln N)/N). Multiplied by NkN - k open cups, the expected new locks per round is Θ(lnN)\Theta(\ln N), and k1/Θ(lnN)N\sum_{k} 1 / \Theta(\ln N) \sim N rounds.

  • The number of throws grows roughly quadratically with NN: each round costs Θ(NlnN)\Theta(N \ln N) throws (cover time on the open cups), and there are Θ(N)\Theta(N) rounds, for Θ(N2lnN)\Theta(N^{2} \ln N) total.

For concrete numbers, a simulation gives E[rounds]8.3\mathbb{E}[\text{rounds}] \approx 8.3 and E[throws]58.8\mathbb{E}[\text{throws}] \approx 58.8 at N=5N = 5; 19.0\approx 19.0 rounds and 292.8\approx 292.8 throws at N=10N = 10. The ratio throws/N2\text{throws} / N^{2} is roughly 2.32.3 at N=5N = 5 and 2.92.9 at N=10N = 10, consistent with the Θ(N2lnN)\Theta(N^{2} \ln N) scaling.

The computation

Encode the round dynamics exactly and run 10410^{4} trials per NN. Confirm rounds scale roughly linearly and throws roughly quadratically.

  1. Maintain a Boolean vector locked[1..N].

  2. Per round: draw labelled balls into uniform cups until every cup has at least one ball (locked or new).

  3. For each new ball in cup cc, if its label equals cc set locked[c] to true.

  4. Stop at the first round where all entries are locked.

import random
from statistics import mean

def play(N, rng):
    locked = [False] * N
    rounds = 0
    throws = 0
    while not all(locked):
        rounds += 1
        touched = list(locked)                       # locked cups count as non-empty
        new_balls = {i: [] for i in range(N) if not locked[i]}
        while not all(touched):
            label = rng.randrange(N)
            cup = rng.randrange(N)
            throws += 1
            touched[cup] = True
            if not locked[cup]:
                new_balls[cup].append(label)
        for cup, balls in new_balls.items():
            if any(b == cup for b in balls):
                locked[cup] = True
    return rounds, throws

rng = random.Random(0)
for N in (3, 5, 7, 10):
    samples = [play(N, rng) for _ in range(10_000)]
    er = mean(r for r, _ in samples)
    et = mean(t for _, t in samples)
    print(f"N={N}: E[rounds]~{er:.2f}, E[throws]~{et:.2f}, "
          f"throws/N^2~{et / N**2:.2f}")

The script confirms that the rounds count grows linearly and the throws count grows quadratically, with the constants slowly creeping up due to the lnN\ln N factor in the cover-time term.

Extra Credit

It’s been a while: time for a Coolest Riddler Extension Award. What if the game weren’t so simple? What if you could aim? What if you could miss? The most interesting twist on this game earns a shiny emoji trophy.

Solution

This is a participatory contest call (Coolest Riddler Extension), not a derivable mathematical question. Like the other community-submission Extras (the trap-effectiveness Express in chapter 204204 being the closest example), it is deferred.