Chapter 54
En Garde! Can You Win The Fencing Relay?
Riddler Express
A single elevator serves four stories: the garage , then floors , , . It is malfunctioning and stops at every floor, forever cycling I head for it on a uniformly random floor and hear the doors close, with no idea where the car is. On average, how many stops until it opens on my floor (counting that stop)? Extra credit: with stories instead of four?
Solution
The car’s route is a cycle of stops: up through and back down to , then again. The garage and the top floor are each visited once per cycle; every floor in between is visited twice. Hearing the doors close tells me only that the car sits at one of the stops, each equally likely, and I count forward to the next stop at my floor.
For a floor visited once per cycle, the forward distances from the possible just-closed positions are in some order, so the wait averages . A floor visited twice splits the cycle into two arcs of lengths and ; the forward wait averages Floor (for ) is reached going up at stop and coming down at stop , so its arcs are and . Averaging the two single-visit floors and the double-visit floors over all starting floors collapses to a strikingly simple result:
The computation
Run the cycle exactly: for every starting floor and every just-closed position, count stops to the next opening on that floor, and average.
from fractions import Fraction as F
def avg(n):
cyc = list(range(n)) + list(range(n - 2, 0, -1)) # floor at each stop
L, total, count = len(cyc), F(0), 0
for my in range(n): # my floor, uniform
for start in range(L): # doors just closed here
k = 1
while cyc[(start + k) % L] != my: k += 1
total += k; count += 1
return total / count
print(avg(4), float(avg(4))) # 17/6, 2.833
print([avg(n) for n in (5, 6, 10)]) # match (4n+1)/6
Riddler Classic
You coach three fencers: the strongest wins each point with probability , the middle one , the weakest . In a relay your first fencer duels until someone reaches , then a second takes over (from the running score) until someone reaches , then the third until someone reaches . You choose the order. Which order, and what is your chance of winning?
Solution
Each leg adds points from the spot the last leg left off, so the order matters even though all three fencers eventually take a turn. The instinct to lead with your best fencer is wrong. Put the weakest first: an early leg can do least harm there, with the whole relay still ahead to recover, while the strongest fencer is most valuable last, fencing the decisive points to . Computing the win probability exactly for all six orders confirms it, with the strongest-last orders far ahead: Swapping the front two to middle-weakest-strongest costs about a point (); leading with the strongest and ending on the weakest collapses to .
The computation
Track the exact score distribution leg by leg: each leg races the running score to its threshold, point by point, and the three legs compose into the final win probability for every ordering.
from fractions import Fraction as F
from itertools import permutations
def leg(p, target, dist): # advance score dist to a threshold
out = {}
for (a, b), pr in dist.items():
cur = {(a, b): pr}
while cur:
nxt = {}
for (x, y), q in cur.items():
if x >= target or y >= target:
out[(x, y)] = out.get((x, y), F(0)) + q
else:
nxt[(x + 1, y)] = nxt.get((x + 1, y), F(0)) + q*p
nxt[(x, y + 1)] = nxt.get((x, y + 1), F(0)) + q*(1 - p)
cur = nxt
return out
P = {'w': F(1, 4), 'm': F(1, 2), 's': F(3, 4)}
res = []
for order in permutations('wms'):
d = {(0, 0): F(1)}
for who, tgt in zip(order, (15, 30, 45)):
d = leg(P[who], tgt, d)
win = sum(pr for (a, b), pr in d.items() if a >= 45)
res.append((order, float(win)))
for o, w in sorted(res, key=lambda r: -r[1]): print(o, round(w, 3))