Can You Win Riddler Jenga?

A FiveThirtyEight Riddler puzzle.

By Vamshi Jandhyala in mathematics Riddler

February 19, 2021

Riddler Express

This time, there are seven three-digit numbers — each belongs in a row of the table below, with one digit per cell. The products of the three digits of each number are shown in the rightmost column. Meanwhile, the products of the digits in the hundreds, tens and ones places, respectively, are shown in the bottom row. Can you find all seven three-digit numbers and complete the table?

Computational Solution

from z3 import *

class MysteriousNumbersSolver:
    def __init__(self):
        self.X = [[Int("self.X_%s_%s" % (i, j)) for j in range(3)]
                      for i in range(7)]
        self.s = Solver()
        self.s.add([And(0 <= self.X[i][j], self.X[i][j]<= 9) for i in range(6) for j in range(1,3)])
        self.s.add([And(0 <= self.X[i][0], self.X[i][0]<= 9) for i in range(6)])

    def set_constraints(self):   
        self.s.add(self.X[0][0]*self.X[0][1]*self.X[0][2]==280)
        self.s.add(self.X[1][0]*self.X[1][1]*self.X[1][2]==168)
        self.s.add(self.X[2][0]*self.X[2][1]*self.X[2][2]==162)
        self.s.add(self.X[3][0]*self.X[3][1]*self.X[3][2]==360)
        self.s.add(self.X[4][0]*self.X[4][1]*self.X[4][2]==60)
        self.s.add(self.X[5][0]*self.X[5][1]*self.X[5][2]==256)
        self.s.add(self.X[6][0]*self.X[6][1]*self.X[6][2]==126)
        self.s.add(self.X[0][0]*self.X[1][0]*self.X[2][0]*self.X[3][0]*self.X[4][0]*self.X[5][0]*self.X[6][0]==183708)
        self.s.add(self.X[0][1]*self.X[1][1]*self.X[2][1]*self.X[3][1]*self.X[4][1]*self.X[5][1]*self.X[6][1]==245760)
        self.s.add(self.X[0][2]*self.X[1][2]*self.X[2][2]*self.X[3][2]*self.X[4][2]*self.X[5][2]*self.X[6][2]==117600)

    def output_solution(self):
        m = self.s.model()
        for i in range(7):
            print(" ".join([str(m.evaluate(self.X[i][j])) for j in range(3)]))

    def solve(self):
        self.set_constraints()
        if self.s.check() == sat:
            self.output_solution()
        else:
            print(self.s)
            print("Failed to solve.")

s = MysteriousNumbersSolver()
s.solve()

Here is the solution:

7 8 5
3 8 7
9 6 3
9 8 5
3 5 4
4 8 8
9 2 7

Riddler Classic

In the game of Jenga, you build a tower and then remove its blocks, one at a time, until the tower collapses. But in Riddler Jenga, you start with one block and then place more blocks on top of it, one at a time.

All the blocks have the same alignment (e.g., east-west). Importantly, whenever you place a block, its center is picked randomly along the block directly beneath it. On average, how many blocks must you place so that your tower collapses — that is, until at least one block falls off?

(Note: This problem is not asking for the average height of the tower after any unbalanced blocks have fallen off. It is asking for the average number of blocks added in order to make the tower collapse in the first place.)

Computational Solution

The key observation is that at some point when the next jenga block is placed on top of the current stable set of blocks, you get a composite block (comprising of two or more contiguous blocks from the top) such that the $x$-coordinate of its center of mass lies outside of the base, i.e. falls outside of the block just below the composite block. The resulting torque topples all the blocks which are a part of the composite block. As per the simulation below, the average number of blocks that one must place in the tower such that atleast one block falls of is $\bf{7.1}$.

from random import uniform

runs = 100000
total_len = 0
for _ in range(runs):
    run_len = 1
    centers = [0]
    brk = False
    while True:
        centers.append(centers[-1] + uniform(-1,1))
        run_len += 1
        for i in range(run_len-1, 0, -1):
            cm_block = sum(centers[i-run_len:])/(run_len-i)
            if  cm_block < centers[i-1]-1 or cm_block > centers[i-1]+1:
                total_len += run_len
                brk = True
                break
        if brk:
            break
print(total_len/runs)