Project Euler Problem 90 Statement
Each of the six faces on a cube has a different digit ($0$ to $9$) written on it; the same is done to a second cube. By placing the two cubes side-by-side in different positions we can form a variety of $2$-digit numbers.
For example, the square number $64$ could be formed:
In fact, by carefully choosing the digits on both cubes it is possible to display all of the square numbers below one-hundred: $01$, $04$, $09$, $16$, $25$, $36$, $49$, $64$, and $81$.
For example, one way this can be achieved is by placing $\{0, 5, 6, 7, 8, 9\}$ on one cube and $\{1, 2, 3, 4, 8, 9\}$ on the other cube.
However, for this problem we shall allow the $6$ or $9$ to be turned upside-down so that an arrangement like $\{0, 5, 6, 7, 8, 9\}$ and $\{1, 2, 3, 4, 6, 7\}$ allows for all nine square numbers to be displayed; otherwise it would be impossible to obtain $09$.
In determining a distinct arrangement we are interested in the digits on each cube, not the order.
- $\{1, 2, 3, 4, 5, 6\}$ is equivalent to $\{3, 6, 4, 1, 2, 5\}$
- $\{1, 2, 3, 4, 5, 6\}$ is distinct from $\{1, 2, 3, 4, 5, 9\}$
But because we are allowing $6$ and $9$ to be reversed, the two distinct sets in the last example both represent the extended set $\{1, 2, 3, 4, 5, 6, 9\}$ for the purpose of forming $2$-digit numbers.
How many distinct arrangements of the two cubes allow for all of the square numbers to be displayed?
Solution
This problem is complicated (about 30 lines, uncommented). So I thought I would document the solution using internal comments instead of discussing all the horrible details in paragraphs of text.
HackerRank version
HackerRank Project Euler 89 (easy? maybe, but very tedious) increases/decreases the number of dice to 1, 2, or 3 cubes to allow for all of the first $N$ square numbers ($1\le N\le 10^{\frac{M}{2}}$)) to be displayed?
Python Source Code
from itertools import combinations
def can(die, digit):
"""
Checks if a digit can be formed by a die, considering 6 and 9 as interchangeable.
Args:
die: A tuple representing the numbers on a die.
digit: The digit to check.
Returns:
True if the digit can be formed, False otherwise.
"""
return digit in die or (digit in (6, 9) and (6 in die or 9 in die))
def check_pair(d1, d2, sq, cache):
"""
Checks if two dice can form all the square numbers.
Args:
d1: A tuple representing the numbers on the first die.
d2: A tuple representing the numbers on the second die.
sq: A list of lists, where each inner list represents the digits of a square number.
cache: A dictionary to store the results of previous checks.
Returns:
True if the dice pair can form all square numbers, False otherwise.
"""
key = (d1, d2) if d1 <= d2 else (d2, d1) # Ensure consistent key order
if key not in cache:
cache[key] = all(
(can(d1, s[0]) and can(d2, s[1])) or (can(d1, s[1]) and can(d2, s[0]))
for s in sq
)
return cache[key]
def PE90(n, m):
"""
Calculates the number of distinct arrangements of dice that can form all square numbers up to n^2.
Args:
n: The upper limit for generating square numbers (up to n^2).
m: The number of digits in the square numbers (e.g., 2 for two-digit squares).
Returns:
The number of distinct arrangements of dice.
"""
dice = list(combinations(range(10), 6)) # Generate all possible 6-sided dice
sq = [[int(x) for x in f"{i*i:0{m}d}"] for i in range(1, n + 1)] # Generate square numbers
cache = {} # Initialize the cache for check_pair results
if m == 1:
# For single-digit squares, we only need to check if each die can form the required digits.
req = {s[0] for s in sq} # Get the unique digits required
return sum(all(can(d, x) for x in req) for d in dice)
if m == 2:
# For two-digit squares, we need to check pairs of dice.
count = 0
for i in range(len(dice)):
for j in range(i, len(dice)):
if check_pair(dice[i], dice[j], sq, cache):
count += 1
return count
# For three-digit or more squares, we need to check combinations of three dice.
count = 0
for i in range(len(dice)):
for j in range(i, len(dice)):
for k in range(j, len(dice)):
if all(
any(
can(dice[p[0]], s[0])
and can(dice[p[1]], s[1])
and can(dice[p[2]], s[2])
for p in ((i,j,k), (i,k,j), (j,i,k), (j,k,i), (k,i,j), (k,j,i))
)
for s in sq
):
count += 1
return count
# Get input from the user
N, M = map(int, input().split())
# Calculate and print the result
print(PE90(N, M))