Chapter 215
525,600 Minutes Of Math
Riddler Express
The song “Seasons of Love” from the musical “Rent” notes that a year has minutes, and indeed . This raises an abstract mathematical question: given any three random integers , , , what are the chances that their product is divisible by ?
The Riddler, FiveThirtyEight, February 8, 2019(original post)
Solution
The puzzle is asking for the natural density of triples with , made precise by sampling the residues of , , independently and uniformly. Because and , divisibility by and divisibility by are independent events (CRT), so Compute the two factors separately by working with the -adic and -adic valuations of , , .
Valuations. For a uniformly random integer (in the natural-density sense) and a prime , the -adic valuation has the distribution which is geometric: an integer is divisible by with probability , and “exactly but no higher” with the displayed product. Because is additive under multiplication, . Divisibility of by is the event .
The factor. For , and . Compute by complement: Hence .
The factor. For , and . By the same complement: Hence .
Combining. By independence,
The computation
Encode the natural-density problem directly: draw , , uniformly from a large range, compute , and tally the empirical hit rate.
Sample many triples uniformly from a large integer range.
Check the product modulo .
Compare the hit rate to the exact rational .
import random
from fractions import Fraction
def simulate(trials=2_000_000, lo=1, hi=10**6, seed=0):
rng = random.Random(seed)
hits = 0
for _ in range(trials):
x = rng.randint(lo, hi)
y = rng.randint(lo, hi)
z = rng.randint(lo, hi)
if (x * y * z) % 100 == 0:
hits += 1
return hits / trials
exact = Fraction(11, 16) * Fraction(113, 625)
print(f"exact = {exact} = {float(exact):.4f}")
print(f"simulated = {simulate():.4f}")
The script prints and a simulated value within at trials, matching the boxed answer.
Riddler Classic
Spread on a table between you are nine index cards numbered through . You take turns picking up cards and putting them in your hand; there is no discarding. The game ends either when no cards remain (a draw) or when one player has any three cards summing to exactly (that player wins). You go first. With perfect play, who wins?
The Riddler, FiveThirtyEight, February 8, 2019(original post)
Solution
The game is tic-tac-toe in disguise on a magic square; with perfect play it is a draw.
The bijection. Arrange the digits as the magic square Every row, column, and diagonal of sums to . Moreover, every three-element subset of that sums to is a row, column, or diagonal of . The eight subsets are exactly the eight lines of the board (three rows, three columns, two diagonals).
The consequence. Picking a card is, by this bijection, placing an or an on the corresponding cell of the tic-tac-toe board. Holding three cards that sum to is the same as occupying a line. The card game is therefore tic-tac-toe played on .
Game value. Tic-tac-toe with perfect play is a draw (standard, known to children; provable by full backward induction on the reachable positions). The same value transfers to the card game by the bijection above. Hence:
The computation
Encode the card game directly (no use of the tic-tac-toe equivalence): play minimax over the full game tree, terminal-test on “some three cards in the current player’s hand sum to ”, and propagate the game value back to the root. The root value confirms the draw.
State (sorted tuple of available cards, sorted tuple of player-’s hand, sorted tuple of player-’s hand, whose turn).
Terminal: the current mover may pick any available card ; after picking, if any three cards in the mover’s hand sum to , the mover wins. If all cards are gone with no winner, it is a draw.
Game value is for player- win, for player- win, for a draw. Player maximises, player minimises.
from functools import lru_cache
from itertools import combinations
def has_15(hand):
return any(a + b + c == 15
for a, b, c in combinations(hand, 3))
@lru_cache(maxsize=None)
def value(avail, h1, h2, turn):
if not avail:
return 0
if turn == 0:
best = -2
for c in avail:
na = tuple(x for x in avail if x != c)
nh = tuple(sorted(h1 + (c,)))
v = 1 if has_15(nh) else value(na, nh, h2, 1)
if v > best:
best = v
if best == 1:
break
return best
else:
best = 2
for c in avail:
na = tuple(x for x in avail if x != c)
nh = tuple(sorted(h2 + (c,)))
v = -1 if has_15(nh) else value(na, h1, nh, 0)
if v < best:
best = v
if best == -1:
break
return best
print("game value (perfect play):",
value(tuple(range(1, 10)), (), (), 0))
The script prints , confirming a draw with perfect play.