|
| 1 | +""" |
| 2 | +Project Euler Problem: https://projecteuler.net/problem=95 |
| 3 | +
|
| 4 | +An amicable chain is a sequence of numbers where each number is the sum of the proper divisors of the previous one, and the chain eventually returns to the starting number. |
| 5 | +
|
| 6 | +The problem is to find the smallest member of the longest amicable chain under a given limit. |
| 7 | +
|
| 8 | +In this implementation, we aim to identify all amicable chains and find the one with the maximum length, while also returning the smallest member of that chain. |
| 9 | +""" |
| 10 | + |
| 11 | +def sum_of_proper_divisors(n): |
| 12 | + """Calculate the sum of proper divisors of n.""" |
| 13 | + if n < 2: |
| 14 | + return 0 # Proper divisors of 0 and 1 are none. |
| 15 | + total = 1 # Start with 1, since it is a proper divisor of any n > 1 |
| 16 | + sqrt_n = int(n**0.5) # Calculate the integer square root of n. |
| 17 | + |
| 18 | + # Loop through possible divisors from 2 to the square root of n |
| 19 | + for i in range(2, sqrt_n + 1): |
| 20 | + if n % i == 0: # Check if i is a divisor of n |
| 21 | + total += i # Add the divisor |
| 22 | + if i != n // i: # Avoid adding the square root twice |
| 23 | + total += n // i # Add the corresponding divisor (n/i) |
| 24 | + |
| 25 | + return total |
| 26 | + |
| 27 | +def find_longest_amicable_chain(limit): |
| 28 | + """Find the smallest member of the longest amicable chain under a given limit.""" |
| 29 | + sum_divisors = {} # Dictionary to store the sum of proper divisors for each number |
| 30 | + for i in range(1, limit + 1): |
| 31 | + sum_divisors[i] = sum_of_proper_divisors(i) # Calculate and store sum of proper divisors |
| 32 | + |
| 33 | + longest_chain = [] # To store the longest amicable chain found |
| 34 | + seen = {} # Dictionary to track numbers already processed |
| 35 | + |
| 36 | + # Iterate through each number to find amicable chains |
| 37 | + for start in range(1, limit + 1): |
| 38 | + if start in seen: # Skip if this number is already processed |
| 39 | + continue |
| 40 | + |
| 41 | + chain = [] # Initialize the current chain |
| 42 | + current = start # Start with the current number |
| 43 | + while current <= limit and current not in chain: |
| 44 | + chain.append(current) # Add the current number to the chain |
| 45 | + seen[current] = True # Mark this number as seen |
| 46 | + current = sum_divisors.get(current, 0) # Move to the next number in the chain |
| 47 | + |
| 48 | + # Check if we form a cycle and validate the chain |
| 49 | + if current in chain and current != start: |
| 50 | + cycle_start_index = chain.index(current) # Find where the cycle starts |
| 51 | + if current in sum_divisors and sum_divisors[current] in chain: |
| 52 | + # This means we have a valid amicable chain |
| 53 | + chain = chain[cycle_start_index:] # Take only the cycle part |
| 54 | + if len(chain) > len(longest_chain): |
| 55 | + longest_chain = chain # Update longest chain if this one is longer |
| 56 | + |
| 57 | + return min(longest_chain) if longest_chain else None # Return the smallest member of the longest chain |
| 58 | + |
| 59 | +def solution(): |
| 60 | + """Return the smallest member of the longest amicable chain under one million.""" |
| 61 | + return find_longest_amicable_chain(10**6) |
| 62 | + |
| 63 | +if __name__ == "__main__": |
| 64 | + smallest_member = solution() # Call the solution function |
| 65 | + print(smallest_member) # Output the smallest member of the longest amicable chain |
0 commit comments