Problem 202: Laserbeam
View on Project EulerProject Euler Problem 202 Solution
EulerSolve provides an optimized solution for Project Euler Problem 202, Laserbeam, with C++, Python, Java, and a step-by-step mathematical explanation.
Problem Summary In the equilateral-triangle mirror system of Problem 202, the task is to count how many admissible laser directions leave through vertex \(C\) after exactly \(R=12017639147\) reflections. The important point is that the implementations do not simulate reflections one by one; they replace the geometric path count by a much smaller arithmetic counting problem. The key reduction is to introduce $n=\frac{R+3}{2}.$ Once that number is known, every valid trajectory is encoded by an integer \(k\) with \(1 \le k \lt n\), and the answer becomes a count of those integers satisfying two precise conditions: one congruence condition picks out the correct exit corner, and one coprimality condition rules out trajectories that would hit a corner too early. Mathematical Approach The arithmetic used by the C++, Python, and Java implementations comes from the standard unfolding of mirror reflections in an equilateral triangle. After unfolding, a zig-zag laser path becomes a straight segment in a triangular lattice, so the geometric question turns into a lattice-point visibility question. Unfolding Reflections Into a Triangular Lattice Reflect the triangle across the side hit by the beam after each bounce. In the unfolded picture, the beam no longer reflects; instead it travels in a straight line across copies of the original triangle....
Detailed mathematical approach
Problem Summary
In the equilateral-triangle mirror system of Problem 202, the task is to count how many admissible laser directions leave through vertex \(C\) after exactly \(R=12017639147\) reflections. The important point is that the implementations do not simulate reflections one by one; they replace the geometric path count by a much smaller arithmetic counting problem.
The key reduction is to introduce
$n=\frac{R+3}{2}.$
Once that number is known, every valid trajectory is encoded by an integer \(k\) with \(1 \le k \lt n\), and the answer becomes a count of those integers satisfying two precise conditions: one congruence condition picks out the correct exit corner, and one coprimality condition rules out trajectories that would hit a corner too early.
Mathematical Approach
The arithmetic used by the C++, Python, and Java implementations comes from the standard unfolding of mirror reflections in an equilateral triangle. After unfolding, a zig-zag laser path becomes a straight segment in a triangular lattice, so the geometric question turns into a lattice-point visibility question.
Unfolding Reflections Into a Triangular Lattice
Reflect the triangle across the side hit by the beam after each bounce. In the unfolded picture, the beam no longer reflects; instead it travels in a straight line across copies of the original triangle. Endpoints that correspond to exiting through a vertex lie on a specific lattice row, and the reduction used by the implementations labels that row by
$R=2n-3 \qquad\Longleftrightarrow\qquad n=\frac{R+3}{2}.$
Using oblique coordinates adapted to the triangular tiling, candidate endpoints on that row can be indexed by a single integer \(k\), with coordinates proportional to \((k,n-k)\) and range
$1 \le k \lt n.$
So the geometric problem has already been compressed to a finite list of possible straight-line directions.
Which Lattice Points Fold Back to the Desired Exit Vertex?
As one moves along the relevant lattice row, the vertex types repeat with period 3. Only one of those three residue classes folds back to the physical exit vertex \(C\). With the indexing convention used in the implementations, the correct class is
$k \equiv 2 \pmod{3}.$
This is why the code does not count all \(k\), but only every third candidate. The congruence is not a generic template; it is the specific signature of which unfolded vertices correspond to the correct corner of the original equilateral triangle.
Why the Visibility Condition Becomes \(\gcd(k,n)=1\)
A straight segment to the candidate point indexed by \(k\) is valid only if it reaches that point before touching any earlier lattice vertex. Otherwise the folded beam would arrive at a corner sooner, so it would not realize exactly \(R\) reflections.
The direction vector is proportional to \((k,n-k)\). That vector is primitive exactly when
$\gcd(k,n-k)=1.$
Because \(\gcd(k,n-k)=\gcd(k,n)\), the visibility condition is equivalent to
$\gcd(k,n)=1.$
Therefore the full counting problem is
$\#\{\,k:1 \le k \lt n,\ k \equiv 2 \pmod{3},\ \gcd(k,n)=1\,\}.$
Counting the Valid \(k\) by Inclusion-Exclusion
Let \(p_1,p_2,\dots,p_r\) be the distinct prime factors of \(n\). To enforce \(\gcd(k,n)=1\), the implementations apply inclusion-exclusion over divisibility by these primes. For a chosen subset \(S\subseteq\{1,\dots,r\}\), define the squarefree divisor
$d=\prod_{i\in S} p_i.$
We count those admissible \(k\) that are also divisible by \(d\). Writing \(k=dt\), the range \(1 \le k \lt n\) becomes
$1 \le t \le \left\lfloor\frac{n-1}{d}\right\rfloor,$
and the residue condition becomes
$dt \equiv 2 \pmod{3}.$
If \(3 \mid d\), this congruence has no solutions. Otherwise \(d\) is invertible modulo 3, so
$t \equiv 2d^{-1} \pmod{3}.$
Since modulo 3 the inverse of 1 is 1 and the inverse of 2 is 2, the code only needs two cases: if \(d \equiv 1 \pmod{3}\), then \(t \equiv 2 \pmod{3}\); if \(d \equiv 2 \pmod{3}\), then \(t \equiv 1 \pmod{3}\).
Now let
$m_d=\left\lfloor\frac{n-1}{d}\right\rfloor.$
If the required residue class begins at the first positive value \(f_d\in\{1,2,3\}\), then the number of valid \(t\) is
$C_d=\begin{cases} 0, & 3 \mid d,\\[4pt] 0, & f_d \gt m_d,\\[4pt] 1+\left\lfloor\frac{m_d-f_d}{3}\right\rfloor, & f_d \le m_d. \end{cases}$
The final count is
$A(n)=\sum_{S\subseteq\{1,\dots,r\}} (-1)^{|S|} C_{d(S)}.$
That formula is exactly what the implementations evaluate.
Worked Example: \(R=67\)
Take a smaller reflection count \(R=67\). Then
$n=\frac{67+3}{2}=35.$
The candidates with the correct exit residue are
$k \in \{2,5,8,11,14,17,20,23,26,29,32\}.$
Now enforce \(\gcd(k,35)=1\). Since \(35=5\cdot 7\), we remove candidates divisible by 5 or 7. Among the listed values, the forbidden ones are \(5,14,20\). No candidate is divisible by \(35\), so inclusion-exclusion gives
$11-2-1+0=8.$
Hence there are 8 admissible trajectories for \(R=67\). This is the same counting mechanism used for the full input; only the value of \(n\) is much larger.
How the Code Works
Phase 1: Reduce the Geometry to the Integer \(n\)
All three implementations begin by replacing the reflection count with \(n=(R+3)/2\). From that point on, the problem is treated entirely arithmetically. The code then factors \(n\) by trial division and stores only the distinct prime divisors, because inclusion-exclusion only needs to know which primes divide \(k\), not their exponents in \(n\).
Phase 2: Enumerate Squarefree Divisors
The C++ and Java implementations enumerate subsets of the distinct prime divisors recursively, while the Python implementation iterates over bitmasks. Each subset determines one squarefree divisor \(d\). The parity of the subset size determines whether that contribution is added or subtracted in the inclusion-exclusion sum.
Phase 3: Count One Arithmetic Progression Per Divisor
For each divisor \(d\), the implementation computes \(m_d=\lfloor(n-1)/d\rfloor\), decides which residue class of \(t\) modulo 3 solves \(dt\equiv 2\pmod{3}\), and counts how many terms of that arithmetic progression lie in \([1,m_d]\). If \(d\) is a multiple of 3, the contribution is immediately zero. After all subsets have been processed, the accumulated inclusion-exclusion total is the answer. One implementation also checks smaller known reflection counts before printing the final result, which confirms that the arithmetic reduction matches the geometry.
Complexity Analysis
If \(n=(R+3)/2\), the factorization step uses trial division up to \(\sqrt{n}\), so its cost is \(O(\sqrt{n})\) in the straightforward implementation used here. After factorization, let \(r\) be the number of distinct prime factors of \(n\). Inclusion-exclusion then visits exactly \(2^r\) subsets.
Each subset contributes only constant-time arithmetic: a few integer divisions, one congruence case split modulo 3, and one add or subtract. Thus the post-factorization cost is \(O(2^r)\), and the memory usage is \(O(r)\). For the actual input, this is tiny compared with any approach that tries to trace reflections directly.