Problem 778: Freshman's Product
View on Project EulerProject Euler Problem 778 Solution
EulerSolve provides an optimized solution for Project Euler Problem 778, Freshman's Product, with C++, Python, Java, and a step-by-step mathematical explanation.
Problem Summary Write every nonnegative integer in decimal, padding with leading zeros when necessary so that all positions line up. The freshman's product multiplies matching digits and keeps only the last digit at each position, with no carries between positions. If $d_j(n)=\left\lfloor\frac{n}{10^j}\right\rfloor \bmod 10,$ then for two numbers $a\odot b=\sum_{j\ge 0}\left(d_j(a)d_j(b)\bmod 10\right)10^j.$ Because multiplication modulo \(10\) is associative at each digit, the same definition extends to \(R\) factors: $n_1\odot n_2\odot \cdots \odot n_R=\sum_{j\ge 0}\left(\prod_{t=1}^R d_j(n_t)\bmod 10\right)10^j.$ The quantity to compute is $F(R,M)=\sum_{0\le n_1,\dots,n_R\le M} n_1\odot n_2\odot \cdots \odot n_R \pmod{10^9+9}.$ A direct enumeration would require \((M+1)^R\) ordered tuples, so the solution instead counts the contribution of each decimal position separately and compresses the repeated digit multiplication into a fixed \(10\times 10\) transition matrix. Mathematical Approach The crucial observation is that the freshman's product never creates carries. Each decimal place can therefore be analyzed independently and then recombined with its place value. Step 1: Separate the Decimal Positions Fix one place \(p=10^j\)....
Detailed mathematical approach
Problem Summary
Write every nonnegative integer in decimal, padding with leading zeros when necessary so that all positions line up. The freshman's product multiplies matching digits and keeps only the last digit at each position, with no carries between positions. If
$d_j(n)=\left\lfloor\frac{n}{10^j}\right\rfloor \bmod 10,$
then for two numbers
$a\odot b=\sum_{j\ge 0}\left(d_j(a)d_j(b)\bmod 10\right)10^j.$
Because multiplication modulo \(10\) is associative at each digit, the same definition extends to \(R\) factors:
$n_1\odot n_2\odot \cdots \odot n_R=\sum_{j\ge 0}\left(\prod_{t=1}^R d_j(n_t)\bmod 10\right)10^j.$
The quantity to compute is
$F(R,M)=\sum_{0\le n_1,\dots,n_R\le M} n_1\odot n_2\odot \cdots \odot n_R \pmod{10^9+9}.$
A direct enumeration would require \((M+1)^R\) ordered tuples, so the solution instead counts the contribution of each decimal position separately and compresses the repeated digit multiplication into a fixed \(10\times 10\) transition matrix.
Mathematical Approach
The crucial observation is that the freshman's product never creates carries. Each decimal place can therefore be analyzed independently and then recombined with its place value.
Step 1: Separate the Decimal Positions
Fix one place \(p=10^j\). For an ordered \(R\)-tuple \((n_1,\dots,n_R)\), the digit contributed at that place is
$g_p(n_1,\dots,n_R)=\left(\prod_{t=1}^R d_j(n_t)\right)\bmod 10.$
So the whole sum splits as
$F(R,M)=\sum_{\substack{j\ge 0\\10^j\le M}} 10^j S_j \pmod{10^9+9},$
where
$S_j=\sum_{0\le n_1,\dots,n_R\le M} g_{10^j}(n_1,\dots,n_R).$
All higher positions vanish automatically, because every number in \([0,M]\) has digit \(0\) there.
Step 2: Count How Often Each Digit Appears at One Place
For a fixed place \(p=10^j\), let \(c_d(p)\) be the number of integers in \([0,M]\) whose digit at that place equals \(d\). Set
$N=M+1,\qquad q=\left\lfloor\frac{N}{10p}\right\rfloor,\qquad u=N\bmod(10p).$
Each complete block of length \(10p\) contributes exactly \(p\) copies of every digit, so the \(q\) full blocks contribute \(qp\). The remaining tail contributes a partial block, giving
$c_d(p)=qp+\min\bigl(\max(u-dp,0),\,p\bigr),\qquad 0\le d\le 9.$
This formula automatically handles leading zeros. For example, if \(M=27\) and \(p=10\), then the tens digit counts are
$c_0(10)=10,\qquad c_1(10)=10,\qquad c_2(10)=8,\qquad c_d(10)=0\ \text{for }d\ge 3,$
because the numbers \(0\) to \(9\) contribute a leading zero in the tens place.
Step 3: Model Repeated Multiplication with 10 Residue States
At one fixed decimal place, only the current product modulo \(10\) matters. Let \(v_t^{(j)}(r)\) be the number of ordered choices of \(t\) numbers from \([0,M]\) whose digitwise product at place \(10^j\) equals \(r\in\{0,\dots,9\}\).
Before choosing any number, the product is the multiplicative identity, so
$v_0^{(j)}(1)=1,\qquad v_0^{(j)}(r)=0\ \text{for }r\ne 1.$
If the current residue is \(r\) and the next chosen digit is \(d\), the new residue is
$r'\equiv rd \pmod{10}.$
This gives a linear transition matrix \(T_j\) of size \(10\times 10\):
$T_j(r',r)=\sum_{\substack{0\le d\le 9\\ rd\equiv r' \pmod{10}}} c_d(10^j).$
Then the state update is simply
$v_{t+1}^{(j)}=T_j\,v_t^{(j)}.$
These entries are counts, not probabilities: every column sums to \(M+1\), because from any current residue there are \(M+1\) possible next numbers.
Step 4: Jump Directly to \(R\) Factors by Matrix Exponentiation
Let \(e_1\) be the basis vector concentrated at residue \(1\). After choosing \(R\) numbers,
$v_R^{(j)}=T_j^R e_1.$
The total digit contribution at place \(10^j\) is therefore
$S_j=\sum_{r=0}^9 r\,v_R^{(j)}(r)=\sum_{r=0}^9 r\,(T_j^R e_1)_r.$
Since \(R\) can be very large, binary exponentiation computes \(T_j^R\) in \(O(10^3\log R)\) operations for that place.
Step 5: Recombine All Decimal Places
Putting everything together gives the exact formula implemented by the program:
$\boxed{F(R,M)=\sum_{\substack{j\ge 0\\10^j\le M}} 10^j \sum_{r=0}^9 r\,(T_j^R e_1)_r \pmod{10^9+9}.}$
The only place-dependent ingredient is the digit-count vector \(c_d(10^j)\); once those ten counts are known, the rest is the same \(10\)-state linear algebra for every decimal position.
Worked Example: \((R,M)=(2,7)\)
Here only the units place exists, because every number from \(0\) to \(7\) has just one relevant digit. The digit counts are
$c_0(1)=c_1(1)=\cdots=c_7(1)=1,\qquad c_8(1)=c_9(1)=0.$
So the desired sum is simply
$F(2,7)=\sum_{a=0}^7\sum_{b=0}^7 (ab\bmod 10).$
Evaluating row by row gives
$\begin{aligned} a=0&:&&0,\\ a=1&:&&0+1+2+3+4+5+6+7=28,\\ a=2&:&&0+2+4+6+8+0+2+4=26,\\ a=3&:&&0+3+6+9+2+5+8+1=34,\\ a=4&:&&0+4+8+2+6+0+4+8=32,\\ a=5&:&&0+5+0+5+0+5+0+5=20,\\ a=6&:&&0+6+2+8+4+0+6+2=28,\\ a=7&:&&0+7+4+1+8+5+2+9=36. \end{aligned}$
Hence
$0+28+26+34+32+20+28+36=204,$
so \(F(2,7)=204\), matching the implementation checkpoint.
How the Code Works
The C++, Python, and Java implementations follow the same pipeline. They iterate over the place values \(1,10,100,\dots\) up to the largest nonzero digit of \(M\). For each place they compute the ten digit frequencies in \([0,M]\) from the block formula above, assemble the \(10\times 10\) transition matrix on residues modulo \(10\), raise that matrix to the \(R\)-th power by binary exponentiation, and read the weighted digit sum from the column corresponding to the initial residue \(1\). That place contribution is then multiplied by the place value and added to the running answer modulo \(10^9+9\). The three implementations differ only in language syntax and integer handling; the mathematics and the sequence of steps are identical.
Complexity Analysis
Let \(L=\lfloor\log_{10} M\rfloor+1\) be the number of decimal positions that must be processed. For one position, computing the digit counts costs \(O(10)\), building the transition matrix costs \(O(10^2)\), and exponentiating a \(10\times 10\) matrix costs \(O(10^3\log R)\). Therefore the total running time is
$O\bigl(L\cdot 10^3\log R\bigr),$
which is effectively \(O(L\log R)\) because the state space size \(10\) is fixed. The memory usage is \(O(10^2)\) for a few small matrices and digit-count vectors.
Footnotes and References
- Problem page: https://projecteuler.net/problem=778
- Positional notation: Wikipedia — Positional notation
- Modular arithmetic: Wikipedia — Modular arithmetic
- Exponentiation by squaring: Wikipedia — Exponentiation by squaring
- Matrix multiplication: Wikipedia — Matrix multiplication