Open Source · 7 Languages · 900+ Problems

Project Euler Solutions

Complete solutions in C++, Python & Java — with step-by-step mathematical explanations

All Problems

Problem 963: Removing Trits

View on Project Euler

Project Euler Problem 963 Solution

EulerSolve provides an optimized solution for Project Euler Problem 963, Removing Trits, with C++, Python, Java, and a step-by-step mathematical explanation.

Problem Summary For each integer \(1\le n\le N\), the implementations do not work with \(n\) directly. Instead they attach to \(n\) a canonical ternary signature \(V(n)\) that preserves the quantity relevant to the problem. After that compression, the task becomes a counting problem: how many unordered pairs \(\{a,b\}\) and \(\{c,d\}\) with entries in \([1,N]\) have the same combined signature? Brute force over all quadruples is impossible. Even brute force over all \(\binom{N+1}{2}\) unordered pairs would be far too large for \(N=100000\). The key observation in the code is that many integers share the same signature, the signature is built recursively from the ternary expansion, and pair equality can therefore be counted class-by-class instead of number-by-number. Mathematical Approach Write the signature as $V(n)=\bigl(f(n),\varepsilon(n),\mathbf{u}(n)\bigr),$ where \(f(n)\) is a nonpositive dyadic rational, \(\varepsilon(n)\in\{0,1\}\) is a parity bit, and \(\mathbf{u}(n)\) is a finite vector of nonnegative integers. The whole solution is built around the fact that this signature can be computed recursively from the ternary digits of \(n\), and that pair signatures combine componentwise. The Ternary Signature Recurrence Let \(n=3q+r\) with \(r\in\{0,1,2\}\)....

Detailed mathematical approach

Problem Summary

For each integer \(1\le n\le N\), the implementations do not work with \(n\) directly. Instead they attach to \(n\) a canonical ternary signature \(V(n)\) that preserves the quantity relevant to the problem. After that compression, the task becomes a counting problem: how many unordered pairs \(\{a,b\}\) and \(\{c,d\}\) with entries in \([1,N]\) have the same combined signature?

Brute force over all quadruples is impossible. Even brute force over all \(\binom{N+1}{2}\) unordered pairs would be far too large for \(N=100000\). The key observation in the code is that many integers share the same signature, the signature is built recursively from the ternary expansion, and pair equality can therefore be counted class-by-class instead of number-by-number.

Mathematical Approach

Write the signature as

$V(n)=\bigl(f(n),\varepsilon(n),\mathbf{u}(n)\bigr),$

where \(f(n)\) is a nonpositive dyadic rational, \(\varepsilon(n)\in\{0,1\}\) is a parity bit, and \(\mathbf{u}(n)\) is a finite vector of nonnegative integers. The whole solution is built around the fact that this signature can be computed recursively from the ternary digits of \(n\), and that pair signatures combine componentwise.

The Ternary Signature Recurrence

Let \(n=3q+r\) with \(r\in\{0,1,2\}\). Start from

$V(0)=\bigl(0,0,\mathbf{0}\bigr).$

Then the recurrence used by all three implementations is

$V(3q+1)=\bigl(f(q)-1,\varepsilon(q),\mathbf{u}(q)\bigr),$

$V(3q+2)=\bigl(f(q),\varepsilon(q)\oplus 1,\mathbf{u}(q)\bigr),$

and

$V(3q)=\begin{cases} \bigl(f(q)+1,\varepsilon(q),\mathbf{u}(q)\bigr), & f(q)\le -2,\\ \bigl(f(q)/2,\varepsilon(q),\mathbf{u}(q)\bigr), & -2<f(q)<0,\\ Z(3q), & f(q)=0. \end{cases}$

The code stores \(f(n)\) after multiplying by a fixed power of two, so that every subtraction, increment, and halving stays exact in integer arithmetic. This works because the first component is always built from the operations \(-1\), \(+1\), and division by 2, so only dyadic rational numbers can appear.

When the Special Branch Appears

The branch \(Z(3q)\) is not an arbitrary exception. It is reached exactly when the ternary expansion of \(n\) contains only the digits \(0\) and \(2\).

The proof is a short induction. From a state with \(f=0\), appending a ternary digit \(2\) keeps \(f=0\), while appending a digit \(0\) also keeps the first component at 0 via the special branch. Appending a digit \(1\) makes the first component negative immediately. Once \(f<0\), none of the three transitions can return it to 0: the \(r=1\) branch decreases it, the \(r=2\) branch leaves it unchanged, and the \(r=0\) branch either adds 1 to a value at most \(-2\) or halves a value in \((-2,0)\), both of which remain negative. Therefore

Equivalently, \(f(n)=0\) if and only if every ternary digit of \(n\) belongs to \(\{0,2\}\).

This is why the solution splits the problem into two regimes: ordinary numbers with \(f(n)<0\), and the special \(0/2\)-digit numbers where the scalar part vanishes and a more detailed combinatorial profile is needed.

The Zero-Count Profile on \(0/2\)-Digit Numbers

Suppose \(n=\sum_{i\ge 0} t_i 3^i\) and every ternary digit satisfies \(t_i\in\{0,2\}\). Then the implementations encode the remaining information as a parity bit together with a vector that records how zeros are distributed to the right of each digit \(2\).

The parity bit is simply

$\varepsilon(n)\equiv \#\{i:t_i=2\}\pmod 2.$

For \(k\ge 1\), define

$u_k(n)=\#\Bigl\{i:t_i=2,\ \#\{j<i:t_j=0\}=k\Bigr\}.$

So \(u_k(n)\) counts how many digits \(2\) have exactly \(k\) zeros somewhere to their right. Digits \(2\) with no zero to their right affect only the parity bit and do not contribute to the stored vector. The implementations reconstruct this profile by scanning the ternary expansion from least significant trit to most significant trit, keeping a running count of zeros already seen.

A few concrete examples make the rule transparent. For \(18=200_3\), the parity bit is 1 and the only nonzero vector entry is \(u_2=1\). For \(20=202_3\), the parity bit is 0 and the only nonzero entry is \(u_1=1\), because one digit \(2\) has one zero to its right and the other has none. For \(24=220_3\), the parity bit is 0 and the only nonzero entry is \(u_1=2\), because both digits \(2\) have exactly one zero to their right.

Combining Two Signatures

Once a number has been replaced by its signature, a pair \(\{a,b\}\) is represented by the componentwise combination

$V(a,b)=\bigl(f(a)+f(b),\ \varepsilon(a)\oplus\varepsilon(b),\ \mathbf{u}(a)+\mathbf{u}(b)\bigr).$

If a signature class \(K\) occurs \(F(K)\) times among \(1,2,\dots,N\), then there is no reason to enumerate all members of that class separately. The number of unordered pairs contributed by one class on the diagonal is

$\binom{F(K)+1}{2},$

while two distinct classes \(K_1\) and \(K_2\) contribute

$F(K_1)F(K_2).$

Accumulating these weights by the resulting pair signature produces a distribution \(C(T)\):

$C(T)=\#\bigl\{\{a,b\}:1\le a\le b\le N,\ V(a,b)=T\bigr\}.$

The final answer is then

$A(N)=\sum_T C(T)^2.$

In other words, the program counts ordered pairs of unordered pairs that land in the same combined signature class.

Worked Example: The Validation Case \(N=5\)

For the first five integers, the signatures are

$V(1)=(-1,0,\mathbf{0}),\quad V(2)=(0,1,\mathbf{0}),\quad V(3)=(-1/2,0,\mathbf{0}),$

$V(4)=(-2,0,\mathbf{0}),\quad V(5)=(-1,1,\mathbf{0}).$

There are 15 unordered pairs \(\{a,b\}\) with \(1\le a\le b\le 5\), but only 12 distinct combined signatures. The collisions are

$V(1,1)=V(5,5),\qquad V(1,5)=V(2,4),\qquad V(2,5)=V(3,3).$

So three signature classes occur twice and nine classes occur once. Therefore

$A(5)=3\cdot 2^2+9\cdot 1^2=21,$

which is exactly the value used by the implementations as their built-in sanity check.

How the Code Works

Building the Signature Table

The C++, Python, and Java implementations compute \(V(n)\) for every \(0\le n\le N\) in increasing order. Because each transition only depends on the quotient \(\lfloor n/3\rfloor\), this is a simple dynamic-programming pass over the integers. The dyadic scalar is stored as a scaled integer, the parity bit is stored explicitly, and the zero-count profile is kept in a short fixed-length vector that is comfortably large enough for \(N=100000\).

The only non-constant-time update is the special \(0/2\)-digit branch for multiples of 3 with \(f=0\). In that case the implementation rescans the ternary digits of the current number to rebuild the zero-count profile. This is deliberate: once the scalar part has collapsed to zero, appending a trailing zero can shift every recorded zero count, so rebuilding from the digits is simpler and safer than trying to update the compressed profile locally.

Compressing Equal Signatures and Counting Pair Classes

After the table of \(V(n)\) values is available, the implementations build a frequency map \(F(K)\) for the distinct signature classes. This compression is substantial: for the target input, the 100000 integers collapse to only a few thousand distinct signatures, so the expensive part of the search moves from numbers to classes.

The next pass loops over unordered pairs of signature classes. For each diagonal class it adds \(\binom{F(K)+1}{2}\), and for each off-diagonal class pair it adds \(F(K_1)F(K_2)\), to the bucket indexed by the combined signature. Once that map has been built, the answer is the sum of the squares of all bucket sizes.

Because the final count is much larger than 64-bit signed range, the implementations use an extended integer type for the last accumulation stage: native arbitrary precision in Python, a big integer type in Java, and a 128-bit unsigned integer in C++.

Complexity Analysis

Let \(U\) be the number of distinct signatures among \(1,\dots,N\). Building the main table is essentially linear in \(N\), with occasional extra \(O(\log_3 N)\) rescans on the special \(0/2\)-digit branch. The compression phase is \(O(N)\). The dominant counting phase is \(O(U^2)\), because every unordered pair of signature classes is processed once, and each combination uses only fixed-size arithmetic on the three signature components.

Memory usage is \(O(N)\) for the table of signatures together with \(O(U)\) and \(O(M)\) for the frequency map and the pair-signature map, where \(M\) is the number of distinct combined signatures that actually occur. For the target input this is practical precisely because \(U\) is far smaller than \(N\), so the program never needs to examine all integer pairs directly.

Footnotes and References

  1. Problem page: https://projecteuler.net/problem=963
  2. Ternary numeral system: Wikipedia - Ternary numeral system
  3. Dyadic rational: Wikipedia - Dyadic rational
  4. Cantor set and ternary digits \(0\) and \(2\): Wikipedia - Cantor set
  5. Discrete convolution as a counting pattern on class frequencies: Wikipedia - Convolution

Mathematical approach · C++ solution · Python solution · Java solution

Previous: Problem 962 · All Project Euler solutions · Next: Problem 964

Need help with a problem? Ask me! 💡
e
✦ Euler GLM 5.2
Hi! I'm Euler. Ask me anything about Project Euler problems, math concepts, or solution approaches! 🧮