Project Euler Problem 95 Solution

Project Euler Problem 95 Solution

Amicable Chains

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

Project Euler Problem 95 Statement

The proper divisors of a number are all the divisors excluding the number itself. For example, the proper divisors of $28$ are $1$, $2$, $4$, $7$, and $14$. As the sum of these divisors is equal to $28$, we call it a perfect number.

Interestingly the sum of the proper divisors of $220$ is $284$ and the sum of the proper divisors of $284$ is $220$, forming a chain of two numbers. For this reason, $220$ and $284$ are called an amicable pair.

Perhaps less well known are longer chains. For example, starting with $12496$, we form a chain of five numbers: $$12496 \to 14288 \to 15472 \to 14536 \to 14264 (\to 12496 \to \cdots)$$

Since this chain returns to its starting point, it is called an amicable chain.

Find the smallest member of the longest amicable chain with no element exceeding one million.

Solution

HackerRank Project Euler Problem 95 overview:

Find the longest amicable chain among numbers up to a given upper bound \( L \) and prints the smallest member of that chain. An amicable chain is formed by repeatedly taking the sum of proper divisors of a number until the sequence loops back to the starting number.

Solution Summary

The divisor sums are computed using a sieve-like method. For each $i$ (from 1 to $L/2$), all its multiples up to $L$ have $i$ added to their divisor sum. For each number $i$ not already in the seen{} cache, a chain is built by repeatedly replacing the current number by the sum of its proper divisors (from the ds[] array). If the chain eventually comes back to $i$, then an amicable chain has been found.

Each number that is part of a computed chain is cached in seen{} with its chain length. This prevents reprocessing numbers that have already been explored. The program keeps track of the longest chain length found and updates the answer to the starting number of that chain. In the end, it outputs the smallest member of the longest amicable chain.

Solution Detail
1. Building the Divisor-Sum Array

First, the program reads the integer \( L \) and creates an array ds[] of length \( L+1 \). This array will hold the sum of proper divisors for each integer from 1 to \( L \).

The nested loops update ds[] by iterating from 1 to \( \lfloor L/2 \rfloor \). For each divisor \( i \), the inner loop adds \( i \) to every multiple of \( i \) (starting from \( 2i \) up to \( L \)).

L = int(input()) 
ds = [0] * (L + 1)
for i in range(1, L // 2 + 1):
    for j in range(i * 2, L + 1, i):
        ds[j] += i
2. Detecting Amicable Chains

Next, the program uses an outer loop over all numbers from 1 to \( L \) to find amicable chains. Three main variables are used:

For each number \( i \) that has not been cached in seen{}, the program initializes:

The while loop continues as long as:

In each iteration, c is added to the chain and the path, then updated to \( ds[c] \).

When the loop exits, if c == i then we have found an amicable chain that loops back to the starting number.

ans, longest, seen = 0, 0, {}
for i in range(1, L + 1):
    if i not in seen:
        ch, c, path = {i}, ds[i], [i]
        while c <= L and c >= i and c not in ch:
            ch.add(c); path.append(c); c = ds[c]
        if c == i:
            l = len(ch)
            if l > longest:
                longest, ans = l, i
            for x in path:
                seen[x] = l
3. Output

Finally, the program prints ans, which is the smallest member of the longest amicable chain discovered.

print(ans)

HackerRank version

HackerRank Project Euler 95: Given a set of $m$ distinct digits, $S$, find the largest possible integer $n$ such that each integer from $1$ to $n$ is expressible using elements of $S$ and following the above rules. If $1$ is also not expressible, output $0$ instead.

Python Source Code

L = int(input())
ds = [0] * (L + 1)
for i in range(1, L // 2 + 1):
    for j in range(i * 2, L + 1, i):
        ds[j] += i
ans, longest, seen = 0, 0, {}
for i in range(1, L + 1):
    if i not in seen:
        ch, c, path = {i}, ds[i], [i]
        while c <= L and c >= i and c not in ch:
            ch.add(c); path.append(c); c = ds[c]
        if c == i:
            l = len(ch)
            if l > longest:
                longest, ans = l, i
            for x in path:
                seen[x] = l
print(ans)	

Last Word

After running this program for different $L$, the following pattern emerged:

n = input()
if n < 284: print 6
elif n < 15472: print 220
elif n < 629072: print 12496
else: print 14316

Here’s a step-by-step explanation:

Expressing the Logic in Mathematical Terms

The solution uses the following piecewise function to determine the answer:

$$ \text{result} = \begin{cases} 6, & \text{if } n < 284,\\[1em] 220, & \text{if } 284 \leq n < 15472,\\[1em] 12496, & \text{if } 15472 \leq n < 629072,\\[1em] 14316, & \text{if } n \geq 629072. \end{cases} $$

Precomputation. Knowing the correct answers for different ranges of n (6, 220, 12496, and 14316) allows for an extremely efficient and simple implementation. When a user inputs a number n, the script simply checks which range it falls into and outputs the corresponding answer. This method is not only fast but also sidesteps the complexity of divisor-sum calculations during runtime.