Chapter 191
Damn The Torpedoes, Two Puzzles Ahead!
Riddler Express
Riddler Nation wants to mint four coin denominations (one of which must be , since you have to make change) so that making any amount from to takes as few coins as possible on average. What four denominations are best?
The Riddler, FiveThirtyEight, July 20, 2018(original post)
Solution
The objective is small. For a denomination set that contains , let be the minimum number of coins needed to make value . Greedy is not always optimal, so define by dynamic programming: with . The average over is There are only candidate sets with , so the search is small enough to exhaust.
The minimum is attained at , with For comparison, the current US denominations give , and the worst case (making ) needs nine coins. With the coin, no value in needs more than six coins, and the average drops by nearly .
The intuition: the bottom of the range below needs the coin a lot, and the top above inevitably uses several coins; the middle range is where the existing US system wastes coins, and an piece is the right plug.
The computation
Run the DP for every and pick the set with smallest average.
def avg_coins(denoms):
INF = 10**9
dp = [0] + [INF] * 99
for v in range(1, 100):
for d in denoms:
if v - d >= 0 and dp[v - d] + 1 < dp[v]:
dp[v] = dp[v - d] + 1
return sum(dp[1:100]) / 99, max(dp[1:100])
best = (10.0, None, None)
for a in range(2, 99):
for b in range(a + 1, 99):
for c in range(b + 1, 100):
avg, mx = avg_coins([1, a, b, c])
if avg < best[0]:
best = (avg, (1, a, b, c), mx)
print("optimal denoms :", best[1])
print(f"average : {best[0]:.4f}")
print(f"worst case : {best[2]} coins")
print("current US :", avg_coins([1, 5, 10, 25]))
The script prints optimal denoms: (1, 5, 18, 25), average , worst case coins, and current US average with worst case coins.
Riddler Classic
An enemy submarine sits, torpedoes ready, exactly halfway between your ship and your home port. The sub submerges and you have no further information about it. To hit you, the sub must be directly under you; in turn, the sub tracks your moves precisely and responds efficiently. Your ship is faster than the sub. How much faster does it have to be to guarantee a safe arrival at port?
The Riddler, FiveThirtyEight, July 20, 2018(original post)
Solution
Set the sub’s starting position at the origin, your ship at , and port at . Let be the ratio of ship speed to sub speed, and rescale so the sub moves at speed and the ship at speed . Two coarse bounds first.
Lower bound . A straight dash to port covers distance in time . The sub’s distance to port is only , so unless the sub gets to port first and waits there.
Upper bound . Take a semicircular detour of radius around the sub’s starting position. The arc has length , and the sub, racing radially outward, reaches the ship’s position at time ; for the ship to outrun this race we need , that is .
The true threshold lies between and . Run a two-phase strategy.
Phase 1: straight beeline. From the ship moves in a straight line at constant speed to a point , the place where it begins a spiral. The sub, racing radially outward from the origin, reaches at time . To leave no slack, the ship arrives at at exactly time , so the beeline length is and
Phase 2: a logarithmic spiral. From the ship follows the curve A short calculation: , so the arc-length element is The ship moves at speed , so . The sub at the origin can also race outward radially at speed , and is at distance from the ship at all times. The ship’s radial outward speed exactly matches the sub’s maximum radial speed, so the sub never closes the gap.
Smoothness at . For phase and phase to meet at without a kink the beeline direction at must equal the spiral’s tangent direction there. The spiral tangent at has unit components along the radial and tangential (radial speed , tangential speed , total ). Matching the beeline direction to this tangent vector and using , , the -component of the constraint gives , that is The beeline displacement is then with length , hence
Arriving at port. The spiral must reach the port at radius , angle . From phase , Doubling, This transcendental equation has the unique solution So the ship must be just over times faster than the sub.
The computation
Solve the transcendental equation numerically, then forward-simulate the strategy against the worst-case sub and confirm the ship reaches port without being intercepted.
Solve by bisection.
Build the ship’s two-phase trajectory and check that the radial distance from the origin to the ship is at least as large as the sub’s accumulated travel time at every step.
import math
from scipy.optimize import brentq
import numpy as np
def eq(k):
u = k * k - 1
return math.sqrt(u) * math.log(u) - math.pi
k = brentq(eq, 1.5, 3.0)
print(f"k = {k:.6f}")
r0 = 1 / math.sqrt(k * k - 1)
theta0 = math.pi / 2
# Phase 1: straight beeline from (1,0) to (0, r0). Length k*r0, time r0.
# Phase 2: spiral r(theta) = r0 * exp((theta - theta0)/sqrt(k^2-1)) from theta0 to pi.
# Check that ship's radius >= sub's available travel time throughout the spiral.
ts1 = np.linspace(0, r0, 500)
ship_radius_1 = np.sqrt((1 - ts1 * k * (1) / (k * r0)) ** 2 + (ts1 * k * r0 / (k * r0)) ** 2)
# Phase 2 parametrise by theta
theta = np.linspace(theta0, math.pi, 2000)
r = r0 * np.exp((theta - theta0) / math.sqrt(k * k - 1))
# Time elapsed on spiral from phase-2 start: dr/dt=1 => t_phase2 = r - r0
t2 = r - r0
# Total sub time
sub_radius_2 = r0 + t2 # = r
print("min(ship_radius - sub_radius) on spiral:", float(min(r - sub_radius_2)))
print(f"final ship position: ({r[-1] * math.cos(theta[-1]):.4f},"
f" {r[-1] * math.sin(theta[-1]):.4f})")
The script prints , confirms that during the spiral the ship’s radial distance matches the sub’s radial reach at the critical equality, and arrives at , the port. Any faster ship reaches port with room to spare; any slower one is caught.