Project Euler Problem 61 Solution

Project Euler Problem 61 Solution

Cyclical Figurate Numbers

by {BetaProjects} | Project Euler & HackerRank
Difficulty: Easy

Project Euler Problem 61 Statement

Triangle, square, pentagonal, hexagonal, heptagonal, and octagonal numbers are all figurate (polygonal) numbers and are generated by the following formulae:

Polygon Formula Sequence
Triangle $P_{3,n} = \frac{n(n+1)}{2}$ $1, 3, 6, 10, 15, \dots$
Square $P_{4,n} = n^2$ $1, 4, 9, 16, 25, \dots$
Pentagonal $P_{5,n} = \frac{n(3n-1)}{2}$ $1, 5, 12, 22, 35, \dots$
Hexagonal $P_{6,n} = n(2n-1)$ $1, 6, 15, 28, 45, \dots$
Heptagonal $P_{7,n} = \frac{n(5n-3)}{2}$ $1, 7, 18, 34, 55, \dots$
Octagonal $P_{8,n} = n(3n-2)$ $1, 8, 21, 40, 65, \dots$

The ordered set of three $4$-digit numbers: $8128$, $2882$, $8281$, has three interesting properties.

  1. The set is cyclic, in that the last two digits of each number is the first two digits of the next number (including the last number with the first).
  2. Each polygonal type: triangle ($P_{3,127}=8128$), square ($P_{4,91}=8281$), and pentagonal ($P_{5,44}=2882$), is represented by a different number in the set.
  3. This is the only set of $4$-digit numbers with this property.

Find the sum of the only ordered set of six cyclic $4$-digit numbers for which each polygonal type: triangle, square, pentagonal, hexagonal, heptagonal, and octagonal, is represented by a different number in the set.

Solution

Project Euler Problem 61 want's us to find a set of six 4-digit numbers where each number is a different type of polygonal number (from triangle to octagonal), and the numbers form a cyclic sequence. Specifically:

1. Initialization
# Set to store valid cycle sums
qx = set()

qx: A set to store the sums of valid cyclic sequences found.

2. Polygonal Number Generator
# Lambda to generate polygonal numbers for n in the range
# t is the polygonal type (3 to 8).
# Formula: P(t, n) = ((t - 2)*n2 + (4 - t)*n) / 2
pn = lambda n: [(t, ((t - 2) * n**2 + (4 - t) * n) // 2) for t in range(3, 9)]

pn: A lambda function that generates polygonal numbers.

3. Recursive Path Finder
def next_path(types, data):
    """
    Recursively search for a cycle using the 'ds' adjacency info.
    - types: polygonal types used so far
    - data: the actual 4-digit numbers in the path
    """
    global qx, ds

    # Base case: Check if a complete cycle is formed
    if len(types) == qn and data[0] // 100 == data[-1] % 100 and len(set(data)) == len(data):
        qx.add(sum(data))
    else:
        # Extend the path with valid next (type, number) pairs
        [next_path(types + [t], data + [n])
         for t, n in ds.get((types[-1], data[-1]), [])
         if t not in types and n not in data]

next_path: A recursive function to build potential cyclic sequences.

4. Generating Polygonal Numbers
# p will hold all (type, number) pairs, starting with n=19 (since smaller n typically produce <1000)
p, n, qn = [], 19, int(input())        # qn is how many polygonal types we need in the cycle
q = list(map(int, input().split()))    # The polygonal types we actually care about (e.g., 3,4,5,6...)

5. Collecting 4-Digit Polygonal Numbers
# Collect all 4-digit polygonal numbers for each relevant type in q.
while n <= 141:  # 141 is a safe upper bound for generating 4-digit polygonal numbers
    p += [
        (t, d) for t, d in pn(n)
        if t in q and 1000 <= d <= 9999 and d % 100 > 9
    ]
    n += 1

Loop from n=19 to n=141: Generates polygonal numbers for each type in q.

6. Building the Adjacency Dictionary
# Build a "graph" (dictionary ds) where each key is (type, number),
# and the value is a list of (next_type, next_number) that connect
# by the last two digits == first two digits criterion.
ds = {}
for t1, d1 in p:
    for t2, d2 in p:
        if t1 != t2 and d1 % 100 == d2 // 100:
            ds[(t1, d1)] = ds.get((t1, d1), []) + [(t2, d2)]

ds: A dictionary representing the adjacency list of a graph.

Nested Loops:

7. Initiating the Recursive Search
# Start a path from each possible (type, number) in ds
[next_path([t], [d]) for t, d in ds]

List Comprehension: Iterates over each (t, d) in ds and starts the recursive search with:

8. Outputting the Results
# Print all possible cycle sums in ascending order
[print(x) for x in sorted(qx)]

Sorting and Printing: Sorts the valid cycle sums stored in qx and prints each sum in ascending order.

How the Code Solves the Problem
  1. Generating Polygonal Numbers:

    The code first generates all relevant 4-digit polygonal numbers for the specified types (q). It ensures that each number is valid (4-digit and has meaningful last two digits).

  2. Building Connections:

    It constructs an adjacency dictionary (ds) where each polygonal number points to potential successors that satisfy the cyclic condition (last two digits match the first two of the next number) and are of different polygonal types.

  3. Recursive Search for Cycles:

    Using the next_path function, the code recursively explores all possible paths through the adjacency dictionary, ensuring that:

    • Each polygonal type is used exactly once.
    • Each number in the cycle is unique.
    • The sequence forms a closed cycle.

  4. Collecting and Outputting Results:

    Valid cycles that meet all criteria have their sums added to the qx set. Finally, the code sorts and prints these sums, providing the solution to the problem.

HackerRank version

HackerRank Project Euler 61: You are given a set of numbers $N\in {3,4,5,6,7,8}$ find the sum of $4-digit$ numbers from $N-gonal$ sets that respect the above property. If there are multiple such numbers print their sums in sorted order..

Click here for Project Euler 61 source code