Project Euler Problem 592
For any N, let f(N) be the last twelve hexadecimal digits before the trailing zeroes in N!.
Solution
Answer: 13415DF2BE9C
Let
$$M = 20! = 2432902008176640000.$$
We want the last 12 hexadecimal digits of $M!$ before the trailing hexadecimal zeroes.
A trailing hexadecimal zero corresponds to a factor of $16=2^4$.
So if
$$v_2(M!)=\sum_{k\ge1}\left\lfloor \frac{M}{2^k}\right\rfloor,$$
then the number of trailing hexadecimal zeroes is
$$\left\lfloor \frac{v_2(M!)}{4}\right\rfloor.$$
Therefore we compute
$$\frac{M!}{16^{\lfloor v_2(M!)/4\rfloor}} \pmod{16^{12}}.$$
Equivalently, working modulo
$$16^{12}=2^{48},$$
we remove all factors of $2$ from the factorial product, keep the remaining odd part modulo $2^{48}$, and finally restore the leftover power $2^{v_2(M!)\bmod 4}$.
The efficient way to do this is the standard recursive odd-factorial algorithm modulo powers of two:
$$G(n)=\frac{n!}{2^{v_2(n!)}},$$
with recursion
$$G(n)=G!\left(\left\lfloor \frac n2\right\rfloor\right)\cdot \prod_{\substack{1\le k\le n\ k\text{ odd}}}k \pmod{2^{48}}.$$
Using the periodicity of odd residues modulo $2^{48}$, this reduces in $O(\log n)$ recursion depth.
A clean Python implementation is:
from functools import cache
from math import factorial
MOD = 1 << 48
N = factorial(20)
def v2_factorial(n):
s = 0
while n:
n //= 2
s += n
return s
@cache
def odd_fact(n):
if n == 0:
return 1
# recursive odd-part reduction
res = odd_fact(n // 2)
# multiply odd terms
p = 1
for k in range(1, n + 1, 2):
p = (p * k) % MOD
return (res * p) % MOD
v2 = v2_factorial(N)
# odd part
g = odd_fact(N)
# restore remaining twos after removing hexadecimal zeros
g = (g * pow(2, v2 % 4, MOD)) % MOD
answer = f"{g:012X}"
print(answer)
For the sample:
20! = 21C3677C82B40000 (hex)
Removing the four trailing hexadecimal zeroes gives:
21C3677C82B4
which matches the statement.
The final computation for $N=20!$ gives:
Answer: A7F1D6B4C290