Digit Factorial Chains
Define the digit factorial function by replacing a number with the sum of the factorials of its digits. Starting from any positive integer, repeated application forms a chain that eventually cycles...
Problem Statement
This archive keeps the full statement, math, and original media on the page.
The number \(145\) is well known for the property that the sum of the factorial of its digits is equal to \(145\): \[1! + 4! + 5! = 1 + 24 + 120 = 145.\] Perhaps less well known is \(169\), in that it produces the longest chain of numbers that link back to \(169\); it turns out that there are only three such loops that exist: \begin {align*} &169 \to 363601 \to 1454 \to 169\\ &871 \to 45361 \to 871\\ &872 \to 45362 \to 872 \end {align*}
It is not difficult to prove that EVERY starting number will eventually get stuck in a loop. For example, \begin {align*} &69 \to 363600 \to 1454 \to 169 \to 363601 (\to 1454)\\ &78 \to 45360 \to 871 \to 45361 (\to 871)\\ &540 \to 145 (\to 145) \end {align*}
Starting with \(69\) produces a chain of five non-repeating terms, but the longest non-repeating chain with a starting number below one million is sixty terms.
How many chains, with a starting number below one million, contain exactly sixty non-repeating terms?
Problem 74: Digit Factorial Chains
Mathematical Analysis
Definition 1 (Digit Factorial Function)
The digit factorial function is defined by:
where is the decimal representation of .
Definition 2 (Chain Length)
The chain length is the number of distinct elements in the sequence before the first repetition.
Theorem 1 (Eventual Periodicity)
For all , the orbit eventually enters a cycle. Moreover, for any , we have .
Proof. A -digit number satisfies (for ) and . For :
Thus for all , and more generally, maps any into within finitely many iterations. Since this is a finite set, the orbit must revisit a value, creating a cycle.
For (at most 7 digits), directly.
Theorem 2 (Complete Classification of Cycles)
The digit factorial function has exactly the following cycles:
- Fixed points (1-cycles): , , , .
- 2-cycle: .
- 3-cycle: .
There are no other cycles.
Proof. Every cycle element must satisfy (since maps any value in this range back into it, and cycle elements are closed under ). The claim is verified by exhaustive computation: iterate from every starting point in and record all cycles encountered.
The fixed points satisfy . One verifies:
- .
- .
- .
- .
The 2-cycle: ; .
The 3-cycle: ; Tracing carefully: ; .
Lemma (Chain Length Recursion)
If and is not part of a cycle, then .
Proof. The chain starting at is . Since is not in a cycle, for all , so the number of distinct terms is .
Corollary (Memoization)
By the Lemma, once is known for some , the chain length of any predecessor (with and not in a cycle) is immediately determined. This enables dynamic programming over the finite state space .
Editorial
The useful fact here is that many starting values quickly merge into the same tail. Once we know the chain length of some intermediate value, every earlier value that reaches it can inherit that information immediately, so memoization saves almost all repeated work.
For each starting number, we repeatedly apply the digit-factorial map while recording the order in which values appear. There are two ways the walk can stop. If it reaches a value already stored in the cache, then the rest of the chain is already known and we can propagate lengths backward through the current path. If it repeats a value inside the current path, then we have found a new cycle: every value on that cycle gets the cycle length, and the values before the cycle get larger lengths according to their distance from it. The candidates are generated by following the chain itself, and the filtering is exactly the first repeated value, which tells us where the non-repeating part ends.
Pseudocode
Precompute the factorials of the digits 0 through 9.
Create an empty cache of chain lengths.
Set the answer count to 0.
For each starting value below one million:
Follow the digit-factorial map, recording values in order and remembering the first position of each value
If the walk reaches a value already stored in the cache:
work backward through the recorded path and assign lengths using the known tail length
Otherwise a value repeats inside the current path:
locate the first occurrence of that repeated value
assign the cycle length to every value on the cycle
then work backward through the prefix, increasing the length by one at each step
If the starting value has chain length 60:
increase the answer count
Return the answer count.
Complexity Analysis
Time: Each value in (where ) is processed at most once due to memoization. Computing costs per evaluation. The main loop iterates times. Total:
where is the cost per digit-factorial computation. Since , this simplifies to .
- Time: .
- Space: for the memoization cache.
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() {
// Precompute digit factorials
int fact[10];
fact[0] = 1;
for (int i = 1; i <= 9; i++) fact[i] = fact[i-1] * i;
auto digit_fact_sum = [&](int n) -> int {
if (n == 0) return 1;
int s = 0;
while (n > 0) {
s += fact[n % 10];
n /= 10;
}
return s;
};
const int LIMIT = 1000000;
const int CACHE_SIZE = 3000001; // f(n) <= 2,540,160 for n < 10^7
vector<int> chain_len(CACHE_SIZE, 0);
int count = 0;
for (int start = 1; start < LIMIT; start++) {
vector<int> chain;
unordered_map<int, int> position;
int cur = start;
while (position.find(cur) == position.end() && !(cur < CACHE_SIZE && chain_len[cur] > 0)) {
position[cur] = (int)chain.size();
chain.push_back(cur);
cur = digit_fact_sum(cur);
}
if (cur < CACHE_SIZE && chain_len[cur] > 0) {
int length = chain_len[cur];
for (int i = (int)chain.size() - 1; i >= 0; i--) {
length += 1;
int val = chain[i];
if (val < CACHE_SIZE)
chain_len[val] = length;
}
} else {
int cycle_start = position[cur];
int cycle_length = (int)chain.size() - cycle_start;
for (int i = cycle_start; i < (int)chain.size(); i++) {
int val = chain[i];
if (val < CACHE_SIZE)
chain_len[val] = cycle_length;
}
int length = cycle_length;
for (int i = cycle_start - 1; i >= 0; i--) {
length += 1;
int val = chain[i];
if (val < CACHE_SIZE)
chain_len[val] = length;
}
}
if (chain_len[start] == 60) count++;
}
cout << count << endl;
return 0;
}
"""
Project Euler Problem 74: Digit Factorial Chains
Count starting numbers below 1,000,000 whose digit factorial chain
contains exactly 60 non-repeating terms.
Uses memoized chain-length computation (Lemma: L(n) = 1 + L(f(n))
when n is not in a cycle).
"""
import math
def solve():
fact = [math.factorial(d) for d in range(10)]
def digit_fact_sum(n):
if n == 0:
return fact[0]
s = 0
while n > 0:
s += fact[n % 10]
n //= 10
return s
LIMIT = 1_000_000
cache = {}
def chain_length(n):
if n in cache:
return cache[n]
chain = []
position = {}
cur = n
while cur not in position and cur not in cache:
position[cur] = len(chain)
chain.append(cur)
cur = digit_fact_sum(cur)
if cur in cache:
length = cache[cur]
for val in reversed(chain):
length += 1
cache[val] = length
return cache[n]
cycle_start = position[cur]
cycle_length = len(chain) - cycle_start
for i in range(cycle_start, len(chain)):
cache[chain[i]] = cycle_length
length = cycle_length
for i in range(cycle_start - 1, -1, -1):
length += 1
cache[chain[i]] = length
return cache[n]
count = 0
for start in range(1, LIMIT):
if chain_length(start) == 60:
count += 1
return count
if __name__ == "__main__":
answer = solve()
assert answer == 402
print(answer)