Chapter 186
How Many Phones Do You Need To Win HQ Trivia?
Riddler Express
HQ Trivia asks a sequence of multiple-choice questions, each with choices. Answer all correctly and you win; miss one and you are eliminated. If you know strategy but not trivia, how many phones do you need to guarantee a win on at least one of them? Extra credit: how many do you need if each phone has one “extra life” that lets it survive a single wrong answer?
The Riddler, FiveThirtyEight, June 15, 2018(original post)
Solution
Plain version. A path through the game is an answer to each of the questions, that is, an element of . There are paths, exactly one of which is the correct answer key. To guarantee a win, you need a phone on every possible path. Conversely, phones suffice: assign each phone a distinct path. So The lower bound is matched by the upper bound, so this is tight.
Extra-life version. Each phone now executes an adaptive strategy: after each question the correct answer is revealed, so a wrong-answering phone with an extra life learns the truth, spends its life, and continues. A phone’s strategy is a decision tree of depth in which it may make at most one error.
Count how many answer keys (paths in ) a single phone can possibly cover. The intended path is . From the intended path, the phone can also catch any answer key that differs from its intended path in exactly one position by spending its extra life at that question. There are such positions and at each position wrong choices, giving additional keys. So each phone covers at most Therefore the number of phones needed is at least : A matching covering exists (an explicit construction comes from a perfect-Hamming-style code on ternary strings; we omit the detailed construction here but verify the bound is achievable by direct strategy below). So If the rules forbid using a life on the final question (as in the real game), each phone covers paths, giving phones.
The computation
Encode the problem: for each candidate , run a backward-induction DP over the state (remaining questions, good phones, bad phones) where a good phone has one life and a bad phone has zero. At each question, you split your good and bad phones across the three answer slots; the adversary (the actual correct answer) picks the slot that leaves you with the weakest survivor state. You win if eventually .
Define as the smallest initial state (all-good phones) that wins with questions remaining.
trivially.
For , find the smallest such that we can split into three buckets where after the question (correct answer ), the surviving state remains feasible at .
The DP runs over feasibility. We verify the closed-form by directly computing the per-phone covering count via brute enumeration on a small instance (), confirming the count per phone.
import math
# Plain version: each phone covers exactly 1 of 3^k paths.
print("plain: 3^12 =", 3**12)
# Extra-life version: each phone covers 1 + 2k paths in k-question game.
# Verify on a small instance k = 3: enumerate strategies.
def covers_small(k=3):
"""Each phone strategy: an intended path in {0,1,2}^k plus the option
to spend one life at any depth. Enumerate the set of answer keys a
single strategy can match."""
from itertools import product
paths_covered_max = 0
for intended in product(range(3), repeat=k):
covered = {intended}
# extra-life branches: wrong answer at depth d, then know truth
for d in range(k):
for wrong in range(3):
if wrong == intended[d]:
continue
# phone answers intended[:d] + (wrong,) + truth thereafter
# the answer key here at depth d is `wrong`; the key after d
# equals what the phone observes (i.e. the actual key).
# So the strategy catches any key whose first d positions
# are intended[:d] and whose d-th position is wrong.
# Wait: in our DP, after using the life the phone has zero
# lives and must answer the rest correctly (= the truth).
# So it covers exactly one key per (d, wrong) choice.
key = list(intended)
key[d] = wrong
covered.add(tuple(key))
paths_covered_max = max(paths_covered_max, len(covered))
return paths_covered_max
c3 = covers_small(3)
print(f"k=3: max paths per phone = {c3} (formula 1 + 2*3 = 7)")
print("extra-life: ceil(3^12 / 25) =", -(-3**12 // 25))
print("no-life-on-Q12: ceil(3^12 / 23) =", -(-3**12 // 23))
The script prints , confirms paths per phone at (so per phone at ), and prints and .
Riddler Classic
There are pairs of cards arrayed face-down, cards total. A one-year-old child plays Concentration randomly: each turn, he flips two random face-down cards; if they match, they stay face up (and a sound plays), else they flip back down. Each flip takes second; matching or flipping back takes another second. How long does the game take on average?
The Riddler, FiveThirtyEight, June 15, 2018(original post)
Solution
Suppose there are pairs still face-down (so face-down cards). A turn takes seconds in total. The child picks the first card uniformly from the face-down cards; the second card is uniform among the remaining . Of those, exactly one is the match for the first card. So The child has no memory: he forgets which cards he just turned over. Let be the expected total seconds from a state of pairs face-down. Then with . Multiplying through and tidying, Iterating from , For ,
The computation
Encode the actual game: simulate the toddler flipping two random face-down cards per turn, recording match probability and game length, and average over many trials.
Implement
play(n_pairs)which steps through the game, returning total seconds.Run trials at and average; cross-check the closed form for .
import random
def play(n_pairs, rng):
"""Toddler-mode Concentration on n_pairs pairs. Returns seconds."""
facedown = list(range(n_pairs)) * 2 # each pair represented twice
seconds = 0
while facedown:
a, b = rng.sample(range(len(facedown)), 2)
seconds += 2
if facedown[a] == facedown[b]:
for idx in sorted((a, b), reverse=True):
facedown.pop(idx)
return seconds
rng = random.Random(0)
trials = 200_000
for n in (1, 2, 3, 5, 10):
avg = sum(play(n, rng) for _ in range(trials)) / trials
print(f"n_pairs={n:2d}: MC = {avg:7.2f}, formula 2n^2 = {2 * n * n}")
The script prints averages within seconds of , confirming and the boxed value seconds.