Problem 559: Permuted Matrices
View on Project EulerProject Euler Problem 559 Solution
EulerSolve provides an optimized solution for Project Euler Problem 559, Permuted Matrices, with C++, Python, Java, and a step-by-step mathematical explanation.
Problem Summary Problem 559 asks for the value of \(Q(50000)\) modulo the prime $$M=1000000123.$$ The matrix-counting statement is encoded through auxiliary quantities \(P(k,r,n)\). The C++, Python, and Java implementations never try to enumerate the matrices directly. Instead, they transform the count into a coefficient-extraction problem built from inverse factorial powers, alternating signs, and one outer sum over \(k\). Mathematical Approach Fix \(n\), \(r\), and \(k\). The implementations first compute a normalized core value and multiply by \((n!)^r\) only at the end. That normalization turns the combinatorial problem into a clean reciprocal-series recurrence. Step 1: Normalize by factorial powers Because \(M\) is prime and the computation only needs factorials up to \(n=50000\lt M\), every \(m!\) is invertible modulo \(M\). Define $$u_m=(m!)^{-r}\pmod{M}\qquad (0\le m\le n).$$ These numbers are the basic weights of the method. All divisions by factorials are replaced by modular inverses, and the missing factor \((n!)^r\) is restored only after the normalized coefficient has been computed. Step 2: Split \(n\) into full \(k\)-blocks and a remainder Write $$n=bk+s,\qquad b=\left\lfloor\frac{n}{k}\right\rfloor,\qquad 0\le s\lt k.$$ The recurrence advances in jumps of size \(k\)....
Detailed mathematical approach
Problem Summary
Problem 559 asks for the value of \(Q(50000)\) modulo the prime
$$M=1000000123.$$
The matrix-counting statement is encoded through auxiliary quantities \(P(k,r,n)\). The C++, Python, and Java implementations never try to enumerate the matrices directly. Instead, they transform the count into a coefficient-extraction problem built from inverse factorial powers, alternating signs, and one outer sum over \(k\).
Mathematical Approach
Fix \(n\), \(r\), and \(k\). The implementations first compute a normalized core value and multiply by \((n!)^r\) only at the end. That normalization turns the combinatorial problem into a clean reciprocal-series recurrence.
Step 1: Normalize by factorial powers
Because \(M\) is prime and the computation only needs factorials up to \(n=50000\lt M\), every \(m!\) is invertible modulo \(M\). Define
$$u_m=(m!)^{-r}\pmod{M}\qquad (0\le m\le n).$$
These numbers are the basic weights of the method. All divisions by factorials are replaced by modular inverses, and the missing factor \((n!)^r\) is restored only after the normalized coefficient has been computed.
Step 2: Split \(n\) into full \(k\)-blocks and a remainder
Write
$$n=bk+s,\qquad b=\left\lfloor\frac{n}{k}\right\rfloor,\qquad 0\le s\lt k.$$
The recurrence advances in jumps of size \(k\). The quotient \(b\) counts how many full \(k\)-steps fit into \(n\), while the remainder \(s\) records the final incomplete part. This is why the formulas separate naturally into a full-block part and a remainder correction.
Step 3: Build the reciprocal power series
Introduce the formal power series
$$F_k(z)=\sum_{j\ge 0}(-1)^j u_{jk}z^j=1-u_k z+u_{2k}z^2-u_{3k}z^3+\cdots.$$
Now define its reciprocal
$$A_k(z)=\frac{1}{F_k(z)}=\sum_{i\ge 0} a_i z^i.$$
Comparing coefficients in \(A_k(z)F_k(z)=1\) gives
$$a_0=1,$$
$$a_i=\sum_{j=1}^{i}(-1)^{j+1}a_{i-j}u_{jk}\qquad (i\ge 1).$$
This alternating convolution is the heart of the algorithm. Once the sequence \(a_0,a_1,\dots,a_b\) is known, the rest is just a short finishing sum.
Step 4: Correct the final partial block
If \(s=0\), the normalized core contribution is simply
$$\gamma(n,k,r)=a_b.$$
If \(s\gt 0\), one shifted series is needed:
$$G_{k,s}(z)=\sum_{j\ge 0}(-1)^j u_{s+jk}z^j.$$
The desired core is then the coefficient of \(z^b\) in the product \(A_k(z)G_{k,s}(z)\):
$$\gamma(n,k,r)=[z^b](A_k(z)G_{k,s}(z))=\sum_{j=0}^{b}(-1)^j a_{b-j}u_{s+jk}.$$
So the same recurrence handles all complete \(k\)-blocks, and the shifted tail series accounts for the leftover \(s\) entries.
Step 5: Recover \(P(k,r,n)\) and assemble \(Q(n)\)
After the normalized core has been computed, the required count is
$$P(k,r,n)=(n!)^r\gamma(n,k,r)\pmod{M}.$$
For the Project Euler target, the implementations set \(r=n\) and sum over every \(k\):
$$Q(n)=(n!)^n\sum_{k=1}^{n}\gamma(n,k,n)\pmod{M}.$$
The full problem is therefore reduced to evaluating one reciprocal-series coefficient for each \(k\), summing those normalized cores, and multiplying once at the end.
Worked Example: \(P(1,2,3)=19\)
Take \(k=1\), \(r=2\), and \(n=3\). Then \(b=3\) and \(s=0\). The relevant weights are
$$u_1=\frac{1}{(1!)^2}=1,\qquad u_2=\frac{1}{(2!)^2}=\frac14,\qquad u_3=\frac{1}{(3!)^2}=\frac1{36}.$$
Now apply the recurrence:
$$a_0=1,$$
$$a_1=a_0u_1=1,$$
$$a_2=a_1u_1-a_0u_2=1-\frac14=\frac34,$$
$$a_3=a_2u_1-a_1u_2+a_0u_3=\frac34-\frac14+\frac1{36}=\frac{19}{36}.$$
Because \(s=0\), we have \(\gamma(3,1,2)=a_3\). Therefore
$$P(1,2,3)=(3!)^2\cdot \frac{19}{36}=36\cdot \frac{19}{36}=19,$$
which matches the built-in checkpoint used by the implementations.
How the Code Works
The C++, Python, and Java implementations follow the same mathematics. They first precompute factorials and inverse factorials, then raise each inverse factorial to the exponent \(r\) to obtain the table \(u_m\). For a fixed \(k\), they fill the coefficient sequence \(a_0,a_1,\dots,a_b\) with the alternating convolution above and evaluate the shifted finishing sum when \(s>0\).
To compute \(P(k,r,n)\), the implementation multiplies the normalized core by \((n!)^r\). To compute \(Q(n)\), it uses \(r=n\), repeats the same per-\(k\) core computation for every \(1\le k\le n\), adds the cores, and only then multiplies by \((n!)^n\). The C++ version parallelizes the outer loop over \(k\), the Java version runs the same logic serially, and the Python version acts as a thin bridge that invokes the compiled C++ solver and returns its numeric result.
Complexity Analysis
Precomputing factorials and inverse factorials costs \(O(n)\). Building the table \(u_m=(m!)^{-r}\) with repeated fast exponentiation costs \(O(n\log r)\). For one fixed \(k\), the recurrence length is \(b=\lfloor n/k\rfloor\), and the nested alternating sum costs \(O(b^2)\) time. Therefore
$$\sum_{k=1}^{n} O\!\left(\left\lfloor\frac{n}{k}\right\rfloor^2\right)=O(n^2),$$
so \(Q(n)\) is evaluated in overall \(O(n^2)\) time. The main tables use \(O(n)\) memory; the multithreaded C++ version adds one reusable work array per worker thread.
Footnotes and References
- Problem page: https://projecteuler.net/problem=559
- Permutation: Wikipedia — Permutation
- Inclusion-exclusion principle: Wikipedia — Inclusion-exclusion principle
- Formal power series: Wikipedia — Formal power series
- Modular multiplicative inverse: Wikipedia — Modular multiplicative inverse
Mathematical approach · C++ solution · Python solution · Java solution
Previous: Problem 558 · All Project Euler solutions · Next: Problem 560