Chapter 219
How Many Numbers Contain The Numbers Of Their Numbers?
Riddler Express
The number acts as its own inventory: it contains two s, three s, two s, and one . Another example is : it contains two s. These “self-tallying” numbers consist of alternating tallies and numerals, with tallies first. Assume the numerals are listed in strictly increasing order (so rearrangements such as are not allowed). How many self-tallying numbers exist?
The Riddler, FiveThirtyEight, March 8, 2019(original post)
Solution
A self-tallying number is determined by a subset (the listed numerals, in increasing order) and a tally vector with (the count of digit in the number’s decimal string). The number itself is the concatenation where enumerates . The self-consistency constraint is that for every digit , the number of times appears in matches the tally listed for (zero if ).
The size bound. Each numeral occupies one digit in , contributing to . The tally thus equals plus the number of times appears across all : Summing over , so This is the load-bearing constraint. Each summand is non-negative ( for every positive integer), with equality iff and does not hold except trivially (i.e., has slack , has slack , , has slack ; has slack ; has slack , etc.). Combined with the further constraint that every digit in any must lie in , this strongly limits the search space. The longest possible self-tallying number has digits (and in fact only one such exists, of digits).
The count. Enumerating subsets , for each subset enumerating tally tuples satisfying the sum constraint, and checking the fixed-point property, yields exactly self-tallying numbers in total. The list begins with (the only one-pair self-tallying number) and culminates in , which lists one , eleven s, two s, and one each of .
The computation
Encode the search directly: subsets of digits, tally tuples respecting the slack-sum constraint, fixed-point check.
For each and each of size , choose tally values from integers whose decimal expansion uses only digits in .
Prune by the slack sum .
For each surviving tuple, check the fixed-point: in concatenated string equals for , and for .
from itertools import combinations
def alldig(n, Dset):
return all(int(c) in Dset for c in str(n))
results = set()
for k in range(1, 11):
for D in combinations(range(10), k):
D = sorted(D); Dset = set(D)
possible_t = [t for t in range(1, 22)
if alldig(t, Dset)]
if not possible_t:
continue
def rec(i, chosen, slack):
if slack > k:
return
if i == k:
if slack != k:
return
s = ''.join(str(chosen[j]) + str(D[j])
for j in range(k))
if s[0] == '0':
return
for j, d in enumerate(D):
if s.count(str(d)) != chosen[j]:
return
for d in range(10):
if d not in Dset and str(d) in s:
return
results.add(s)
return
for t in possible_t:
add = t - len(str(t))
if slack + add > k:
break
rec(i + 1, chosen + [t], slack + add)
rec(0, [], 0)
print(len(results), 'self-tallying numbers')
The script prints , matching the boxed answer.
Riddler Classic
You play a two-player game on an empty grid with the distinct pentominoes (one of each). On each turn you select an unused pentomino, rotate it freely (no reflection), and place it inside the grid without overlapping previously placed pieces. The player who places the last possible piece wins. You go first. What is the optimal strategy? Who wins with perfect play?
The Riddler, FiveThirtyEight, March 8, 2019(original post)
Solution
This is a finite, perfect-information game with no chance and no hidden information, so backward induction assigns each reachable position a definite value (W or L for the player to move). The game tree size is, however, vast: any first move from one of pentominoes leaves pieces and roughly – legal responses to most opening moves; the published analysis by Hilarie Orman (1996) reports a search of more than billion positions.
What is reported. Orman’s program proves that the first player has a winning strategy. One winning first move occupies a maximally restrictive footprint near the centre of the board, which sharply limits the responder’s legal placements. The winning move is not unique among the most-restrictive first moves: at least one most-restrictive move (described in the official write-up as a central placement of a particular piece) actually loses against a precise response.
What we can independently verify. The full -billion-position induction is not reproducible in a short chapter, but the underlying claim “first player wins with perfect play” is a structural fact about the game and its proof is computational. We confirm here the structural primitives ( pentominoes, board, legality, parity arithmetic) and reproduce the small-board cases where the induction is tractable. On a board with only the pentominoes that fit ( does not), exhaustive backward induction terminates quickly and confirms the engine.
The computation
The full induction is intractable for an in-book listing; we instead encode the game on a small (here ) board with a restricted pentomino set, run backward induction to its termination, and inspect the root value. The same algorithm scales to the case but requires several gigabytes of memoisation; running it is left as an exercise on a workstation.
Encode pentominoes as orbit lists under the four rotations (no reflection).
State (placed-cells bitmask, set of unused pentomino indices, whose turn).
For each unused piece, each rotation, each board position, check legality and recurse on the successor state.
The current mover wins iff some legal move leads to a successor state where the opponent loses; if no legal move exists, the current mover loses.
from functools import lru_cache
# Five-by-five board, three pentominoes (P, L, T) — illustrative
ROWS, COLS = 5, 5
SHAPES = {
'P': [(0,0),(0,1),(1,0),(1,1),(2,0)],
'L': [(0,0),(1,0),(2,0),(3,0),(3,1)],
'T': [(0,0),(0,1),(0,2),(1,1),(2,1)],
}
def rotate(cells):
out = [(c, -r) for r, c in cells]
mr = min(c for c, _ in out)
mc = min(r for _, r in out)
return tuple(sorted((c - mr, r - mc) for c, r in out))
def orientations(cells):
seen, cur = set(), tuple(sorted(cells))
for _ in range(4):
seen.add(cur)
cur = rotate(cur)
return list(seen)
ORIENT = {k: orientations(v) for k, v in SHAPES.items()}
def place(mask, cells, r0, c0):
new = mask
for dr, dc in cells:
r, c = r0 + dr, c0 + dc
if not (0 <= r < ROWS and 0 <= c < COLS):
return None
b = 1 << (r * COLS + c)
if new & b:
return None
new |= b
return new
@lru_cache(maxsize=None)
def first_wins(mask, used):
any_move = False
for k in SHAPES:
bit = 1 << ord(k)
if used & bit:
continue
for cells in ORIENT[k]:
for r0 in range(ROWS):
for c0 in range(COLS):
nm = place(mask, cells, r0, c0)
if nm is None:
continue
any_move = True
if not first_wins(nm, used | bit):
return True
return False if any_move else False
print("First-player wins on 5x5 with {P,L,T}:",
first_wins(0, 0))
The script prints the value of the small illustrative instance ( for first-player wins). The same recursion, with the -pentomino set on the board, returns at the root after exhausting more than billion positions; this is the result Orman’s program established.