Books · The Fiddler: Solutions
Chapter 76
Did xkcd Get Its Math Right?
From Seth Cohen comes a puzzle that fact-checks a webcomic. Two quantities are in play. Let be the probability that, drawing two arrows at random from a quiver of ten (of which five are cursed), neither drawn arrow is cursed. Let be the probability that rolling three six-sided dice and one four-sided die gives a total of at least .
Does equal ? If so, what is their common value? If not, what is each?
The Fiddler, Zach Wissner-Gross, November 29, 2024(original post)
Solution
The arrows come first. Two of the ten are drawn, and the favourable draws are the pairs taken from the five uncursed arrows: For the dice, three and one give equally likely outcomes. Convolving the distribution of with the and counting those at or more gives exactly , so They match: The comic’s two side-by-side mechanisms really do share the same probability.
The computation
Count both directly: the uncursed pairs over all pairs for , and the outcomes reaching for .
from fractions import Fraction as F
from math import comb
from collections import Counter
p = F(comb(5, 2), comb(10, 2)) # uncursed pairs / all pairs
d3 = Counter()
for a in range(1, 7):
for b in range(1, 7):
for c in range(1, 7): d3[a + b + c] += 1
ge = tot = 0
for s, w in d3.items():
for d in range(1, 5):
tot += w
if s + d >= 16: ge += w
q = F(ge, tot)
print(p, q, p == q, f"{ge}/{tot}") # 2/9 2/9 True 192/864
Extra Credit
Using the same probability , how many ways can you simulate it by rolling up to four dice from a standard set () and asking for a sum at or above some threshold, and what are they?
Solution
Searching every multiset of one to four dice and every threshold, exactly three combinations reproduce : The first is the comic’s own; the other two are alternative four-die machines for the same .
The computation
Build each candidate’s sum distribution by convolution, then test every threshold against the target probability.
from itertools import combinations_with_replacement as cwr
DICE = {'d4': 4, 'd6': 6, 'd8': 8, 'd10': 10, 'd12': 12, 'd20': 20}
def dist(dl):
d = Counter({0: 1})
for nm in dl:
nd = Counter()
for s, c in d.items():
for f in range(1, DICE[nm] + 1): nd[s + f] += c
d = nd
return d
target = F(2, 9); ways = []
for k in range(1, 5):
for combo in cwr(DICE, k):
d = dist(combo); tot = sum(d.values())
for T in range(min(d) + 1, max(d) + 1):
if F(sum(c for s, c in d.items() if s >= T), tot) == target:
ways.append(('+'.join(combo), T))
for combo, T in ways: print(combo, ">=", T) # the three machines above