Skip to content
Vamshi Jandhyala

Books · The Riddler

Chapter 243

Can You Win The Tour de FiveThirtyEight?

Riddler Express

A logo is two overlapping circles of radius 11, creating three regions: the lens shared by both circles and the two crescents belonging to one circle only. For all three regions to have equal area, how far apart must the centres be? Extra credit: for three symmetric unit circles (seven regions), what centre distance makes the largest and smallest of the seven regions as close in area as possible?

The Riddler, FiveThirtyEight, September 20, 2019(original post)

Solution

Each circle (area π\pi) is split into the lens plus one crescent, so crescent == π\pi minus lens. For the lens and each crescent to be equal we need the lens to be exactly half the circle: L=πL    L=π2.L = \pi - L \;\Longrightarrow\; L = \tfrac{\pi}{2}. The lens area for two unit circles whose centres are dd apart is L(d)=2cos1 ⁣(d2)d24d2.L(d) = 2\cos^{-1}\!\left(\tfrac{d}{2}\right) - \tfrac{d}{2}\sqrt{4 - d^2}. Setting L(d)=π2L(d) = \tfrac{\pi}{2} has no closed form, but solving numerically gives d0.8079455 inches.\boxed{d \approx 0.8079455 \text{ inches}}. The extra credit has a pretty punchline: arranging three unit circles to even out the seven regions gives the same distance, about 0.80794550.8079455. The reason is that balancing the three-circle regions reduces, by a short area argument, to the very same condition that the two-circle lens be half a circle.

The computation

Encode the lens-area formula and solve L(d)=π/2L(d) = \pi/2 for dd; confirm the lens then equals each crescent.

import numpy as np
from scipy.optimize import brentq
L = lambda d: 2 * np.arccos(d / 2) - (d / 2) * np.sqrt(4 - d * d)
d = brentq(lambda d: L(d) - np.pi / 2, 0.01, 1.99)
print(f"d = {d:.7f}")
print(f"lens = {L(d):.6f}, crescent = {np.pi - L(d):.6f}")
# d = 0.8079455
# lens = 1.570796, crescent = 1.570796   (both = pi/2)

Riddler Classic

In a team time trial with 2020 equally-matched teams, each team picks a pace; a faster pace means a higher chance of “cracking” (not finishing). Teams ride one at a time and see all previous results, and the fastest finisher wins. Team Riddler rides first. To maximise its chance of winning, what is its probability of finishing, and its probability of winning? Extra credit: what are its chances if it rides last instead?

The Riddler, FiveThirtyEight, September 20, 2019(original post)

Solution

Let Team Riddler finish with probability pp. Going first, it can only win if it finishes and every later team fails to beat it. But any team that follows will simply ride a hair faster than the current best pace, so each of the other 1919 teams also finishes (and thereby wins) with essentially probability pp, and fails with probability 1p1 - p. Since the teams are independent, P(win)=p(1p)19.P(\text{win}) = p\,(1 - p)^{19}. Maximising over pp (set the derivative to zero) gives p=120p = \tfrac{1}{20}, so

In general the first of NN teams should finish with probability 1/N1/N. For the extra credit, ride last: by the same rule, whichever team is first-to-go among those still without a finisher aims for finish-probability 1/k1/k with kk teams left, and a short calculation shows each of teams 111919 is equally likely (120\tfrac1{20}) to be the first to finish. Summing Team Riddler’s chance of then beating that leader gives 120(11+12++120)=H202018.0%,\frac{1}{20}\left(\frac{1}{1} + \frac{1}{2} + \cdots + \frac{1}{20}\right) = \frac{H_{20}}{20} \approx 18.0\%, about ten times better: going last, you know exactly what pace you must beat.

The computation

Encode the sequential game and its optimal strategy: with no finisher yet and kk teams left, the rider aims for finish-probability 1/k1/k; once someone has finished, later riders match that pace (finishing makes them the new leader). Simulate with Team Riddler first and last, and check against p(1p)19p(1-p)^{19} and H20/20H_{20}/20.

import random
N = 20
def simulate(riddler_pos, trials, seed):
    rng = random.Random(seed); wins = 0
    for _ in range(trials):
        leader_q = None; leader = None
        for i in range(N):
            q = (1.0 / (N - i)) if leader_q is None else leader_q
            if rng.random() < q:
                leader_q = q; leader = i      # finishes, becomes the new leader
        wins += (leader == riddler_pos)
    return wins / trials

print(f"first: analytic {(1/N) * (1 - 1/N) ** (N - 1):.4f}, "
      f"MC {simulate(0, 2_000_000, 1):.4f}")
H = sum(1 / k for k in range(1, N + 1))
print(f"last:  analytic {H / N:.4f}, MC {simulate(N - 1, 2_000_000, 2):.4f}")
# first: analytic 0.0189, MC 0.0189
# last:  analytic 0.1799, MC 0.1797

The simulation matches: first to ride, Team Riddler wins about 1.89%1.89\% of the time; last to ride, about 18%18\%.