Amicable Chains
Define s(n) = sigma(n) - n, the sum of proper divisors of n. An amicable chain of length k is a cycle n_1 -> n_2 ->... -> n_k -> n_1 under the map s, where all n_i are distinct. Find the smallest e...
Problem Statement
This archive keeps the full statement, math, and original media on the page.
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.
Problem 95: Amicable Chains
Mathematical Foundation
Definition 1 (Sum of proper divisors). For , define
Note .
Definition 2 (Amicable chain). An amicable chain of length is a sequence of distinct positive integers satisfying for and . The cases (perfect numbers) and (amicable pairs) are classical.
Theorem 1 (Divisor sum sieve). The function for all can be computed in time and space.
Proof. Initialize an array . For each , add to for . After this process, because every proper divisor of is enumerated exactly once when we process divisor and add it to .
The total number of additions is , where is the -th harmonic number.
Lemma 1 (Functional iteration and cycle detection). Let be a function on a finite set . For any , the sequence eventually enters a cycle. If we track visited elements and the sequence returns to , the elements visited form an amicable chain containing .
Proof. Since is finite, the sequence must revisit some element. If the first revisited element is itself, the trajectory from back to forms a cycle, which is an amicable chain by definition. If the first revisited element is some , then is on a “tail” leading into the cycle at and is not part of any cycle.
Theorem 2 (Amortized complexity of chain detection). Using a visited-flag array, the total work for chain detection across all starting points is .
Proof. Once a number is marked visited, it is never the starting point of a new exploration and causes any exploration encountering it to terminate immediately. Each number is visited at most once as a “fresh” element during some exploration and at most once as a “termination trigger” for a different exploration. Hence the total number of steps across all explorations is at most .
Editorial
The map turns the problem into a functional graph on the integers up to . The first phase builds that graph efficiently with a divisor-sum sieve, so after that every node has exactly one outgoing edge.
The second phase is then standard cycle hunting in a functional graph. Starting from each unseen value, we follow successors until one of three things happens: the chain leaves the allowed range, it reaches a node already settled by an earlier exploration, or it revisits a node from the current walk. Only the third case creates a new amicable chain. The local position map identifies the cycle portion exactly, and the global visited array prevents the same tail from being explored again.
Pseudocode
Compute the sum of proper divisors $s(n)$ for every $n \le N$ with a sieve.
Create a global visited array.
Set the best chain length and best minimum element to 0.
For each starting value from 2 to $N$:
If it is already globally visited, skip it
Follow the map $n \mapsto s(n)$, recording:
the current chain in order
the position where each value first appears
Stop when the chain leaves the range, reaches a globally visited value, or repeats a value from the current walk
If a repeat occurred inside the current walk:
extract the cycle part
update the best answer if this cycle is longer
Mark every value from the explored walk as globally visited
Return the smallest element from the longest cycle found.
Complexity Analysis
Time: for the sieve (Theorem 1) plus for cycle detection (Theorem 2). Total: .
Space: for the arrays and visited.
Answer
Code
Each problem page includes the exact C++ and Python source files from the local archive.
#include <bits/stdc++.h>
using namespace std;
int main() {
const int LIMIT = 1000000;
// Phase 1: Sieve for sum of proper divisors
vector<int> s(LIMIT + 1, 0);
for (int i = 1; i <= LIMIT; i++)
for (int j = 2 * i; j <= LIMIT; j += i)
s[j] += i;
// Phase 2: Detect longest amicable chain
vector<bool> visited(LIMIT + 1, false);
int bestLen = 0, bestMin = 0;
for (int start = 2; start <= LIMIT; start++) {
if (visited[start]) continue;
vector<int> chain;
unordered_map<int, int> positions;
int n = start;
while (n >= 1 && n <= LIMIT && !visited[n] && !positions.count(n)) {
positions[n] = (int)chain.size();
chain.push_back(n);
n = s[n];
}
// Check for cycle
if (n >= 1 && n <= LIMIT && positions.count(n)) {
int cycleStart = positions[n];
int cycleLen = (int)chain.size() - cycleStart;
int minElem = *min_element(chain.begin() + cycleStart, chain.end());
if (cycleLen > bestLen) {
bestLen = cycleLen;
bestMin = minElem;
}
}
for (int x : chain) visited[x] = true;
}
cout << bestMin << endl;
return 0;
}
"""
Project Euler Problem 95: Amicable Chains
Find the smallest member of the longest amicable chain with no element
exceeding one million.
Phase 1: Sieve for sum of proper divisors s(n) in O(N log N).
Phase 2: Detect cycles via functional iteration in O(N) total.
Answer: 14316
"""
def solve(limit=1_000_000):
# Phase 1: sieve for sum of proper divisors
s = [0] * (limit + 1)
for i in range(1, limit + 1):
for j in range(2 * i, limit + 1, i):
s[j] += i
# Phase 2: detect longest cycle
visited = [False] * (limit + 1)
best_len = 0
best_min = 0
for start in range(2, limit + 1):
if visited[start]:
continue
chain = []
positions = {}
n = start
while True:
if n < 1 or n > limit or visited[n]:
break
if n in positions:
break
positions[n] = len(chain)
chain.append(n)
n = s[n]
# Check if we found a cycle
if n in positions:
cycle_start = positions[n]
cycle = chain[cycle_start:]
if len(cycle) > best_len:
best_len = len(cycle)
best_min = min(cycle)
for x in chain:
visited[x] = True
return best_min
if __name__ == "__main__":
answer = solve()
assert answer == 14316
print(answer)