Open Source · 7 Languages · 900+ Problems

Project Euler Solutions

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

All Problems

Problem 878: XOR-Equation B

View on Project Euler

Project Euler Problem 878 Solution

EulerSolve provides an optimized solution for Project Euler Problem 878, XOR-Equation B, with C++, Python, Java, and a step-by-step mathematical explanation.

Problem Summary Let \(\otimes\) denote carry-less multiplication on nonnegative integers, meaning that binary expansions are multiplied as polynomials over \(\mathbb{F}_2\), and let \(\oplus\) denote bitwise XOR. The quadratic form in the problem is $f(a,b)=a\otimes a \oplus \left(2\otimes a\otimes b\right)\oplus b\otimes b.$ For given bounds \(n\) and \(m\), we must count $C(n,m)=\#\left\{(a,b)\in \mathbb{Z}_{\ge 0}^2: 0\le a\le b\le n,\ f(a,b)\le m\right\}.$ A direct enumeration would inspect about \(n^2/2\) ordered pairs, which is hopeless for the actual input. The solution instead exploits an invariant orbit structure that lets us count whole families of pairs from a small set of canonical seeds. Mathematical Approach The key observation is that carry-less arithmetic is ordinary polynomial arithmetic over \(\mathbb{F}_2\). Once the expression is rewritten in that language, the recurrence used by the implementation becomes natural and its invariant becomes easy to verify. Step 1: Rewrite the quadratic form over \(\mathbb{F}_2[t]\) Write the binary expansions of \(a\) and \(b\) as polynomials $A(t)=\sum_{i\ge 0} a_i t^i,\qquad B(t)=\sum_{i\ge 0} b_i t^i,$ with coefficients in \(\mathbb{F}_2\). Under this identification, XOR is polynomial addition and carry-less multiplication is ordinary multiplication....

Detailed mathematical approach

Problem Summary

Let \(\otimes\) denote carry-less multiplication on nonnegative integers, meaning that binary expansions are multiplied as polynomials over \(\mathbb{F}_2\), and let \(\oplus\) denote bitwise XOR. The quadratic form in the problem is

$f(a,b)=a\otimes a \oplus \left(2\otimes a\otimes b\right)\oplus b\otimes b.$

For given bounds \(n\) and \(m\), we must count

$C(n,m)=\#\left\{(a,b)\in \mathbb{Z}_{\ge 0}^2: 0\le a\le b\le n,\ f(a,b)\le m\right\}.$

A direct enumeration would inspect about \(n^2/2\) ordered pairs, which is hopeless for the actual input. The solution instead exploits an invariant orbit structure that lets us count whole families of pairs from a small set of canonical seeds.

Mathematical Approach

The key observation is that carry-less arithmetic is ordinary polynomial arithmetic over \(\mathbb{F}_2\). Once the expression is rewritten in that language, the recurrence used by the implementation becomes natural and its invariant becomes easy to verify.

Step 1: Rewrite the quadratic form over \(\mathbb{F}_2[t]\)

Write the binary expansions of \(a\) and \(b\) as polynomials

$A(t)=\sum_{i\ge 0} a_i t^i,\qquad B(t)=\sum_{i\ge 0} b_i t^i,$

with coefficients in \(\mathbb{F}_2\). Under this identification, XOR is polynomial addition and carry-less multiplication is ordinary multiplication. Therefore the integer quantity \(f(a,b)\) is the binary encoding of

$Q(A,B)=A(t)^2+tA(t)B(t)+B(t)^2.$

The factor \(2\) becomes \(t\), because shifting left by one bit corresponds to multiplying by \(t\).

Step 2: Use a recurrence that preserves the invariant

Consider the map

$T(A,B)=\left(B,\ tB+A\right).$

In integer language this is exactly

$T(a,b)=\left(b,\ (2b)\oplus a\right).$

Now compute the value of \(Q\) after one step:

$\begin{aligned} Q(T(A,B))&=B^2+tB(tB+A)+(tB+A)^2 \\ &=B^2+t^2B^2+tAB+t^2B^2+A^2 \\ &=A^2+tAB+B^2 \\ &=Q(A,B). \end{aligned}$

So the value \(f(a,b)\) is constant along the entire orbit generated by repeatedly applying \(T\). Each admissible pair belongs to exactly one such invariant orbit.

Step 3: Pick one canonical seed from each orbit

The inverse transformation is obtained by solving \(T(x,y)=(a,b)\). Since \(a=y\) and \(b=2y\oplus x\), the predecessor of \((a,b)\) is

$T^{-1}(a,b)=\left(b\oplus(2a),\ a\right).$

If this predecessor is still ordered, meaning

$b\oplus(2a)\le a,$

then the current pair is not the first ordered point on its orbit and must not be counted as a seed. Hence every nonzero ordered pair is a canonical seed precisely when

$b\oplus(2a) > a.$

This rule removes duplicates: exactly one ordered representative survives per orbit. The pair \((0,0)\) is special, because it is a fixed point and is counted directly.

Step 4: Restrict the seed search using the size of \(m\)

If \(m>0\) has \(d\) binary digits, the implementations only scan ordered pairs with

$0\le a\le b \lt 2^{\lceil(d+1)/2\rceil}.$

This is a safe finite search region for seeds. Intuitively, \(f(a,b)\) is a quadratic carry-less form, so its bit-length is roughly twice the bit-length of the inputs. Once \(b\) is above this threshold, an ordered seed cannot have invariant value at most \(m\), so it can never contribute to the answer.

Step 5: Count all forward orbit points with \(b\le n\)

Starting from a canonical seed, repeatedly apply

$ (a,b)\mapsto \left(b,\ (2b)\oplus a\right). $

For ordered nonnegative pairs, the new second component is strictly larger than the old one: the highest bit of \(2b\) lies one position above every bit of \(a\), so it cannot be canceled by XOR with \(a\). Therefore the orbit walk is strictly increasing in \(b\), and we only need to count how many orbit points satisfy

$b\le n.$

No further value test is needed during that walk, because the invariant \(f(a,b)\) stays constant along the orbit.

Worked Example: \(n=5,\ m=1\)

Here \(m\) has one binary digit, so the seed scan only needs \(b \lt 2\). The ordered candidates are

$ (0,0),\ (0,1),\ (1,1). $

Their invariant values are

$f(0,0)=0,\qquad f(0,1)=1,\qquad f(1,1)=2.$

Thus only \((0,0)\) and \((0,1)\) survive the filter \(f\le 1\). The point \((0,0)\) contributes once. For \((0,1)\), the predecessor is

$\left(1\oplus 0,\ 0\right)=(1,0),$

which is not ordered, so \((0,1)\) is the canonical seed of its orbit. Iterating forward gives

$ (0,1)\to(1,2)\to(2,5)\to(5,8)\to\cdots $

Keeping only terms with second component at most \(5\), we obtain the three valid orbit points \((0,1)\), \((1,2)\), and \((2,5)\). Therefore

$C(5,1)=1+3=4.$

How the Code Works

The C++, Python, and Java implementations all follow the same high-level routine. They first handle the degenerate case \(m=0\), where the only admissible pair is \((0,0)\). Otherwise they derive the power-of-two seed limit from the binary length of \(m\) and scan every ordered pair in that seed box.

For each candidate, the implementation evaluates the carry-less quadratic form and discards the pair immediately if the value exceeds \(m\). Among the survivors, \((0,0)\) is counted directly, while every other pair is checked against the predecessor condition \(b\oplus(2a)\le a\). If that inequality holds, the pair is an interior orbit point and is skipped; if it fails, the pair is the unique canonical seed of its orbit.

From each canonical seed, the implementation repeatedly applies the orbit recurrence, increments the answer once per visited state, and stops as soon as the second component becomes larger than \(n\). Because the invariant is preserved, the forward walk needs only the bound check on \(b\).

Complexity Analysis

Let

$S=2^{\lceil(d+1)/2\rceil},$

where \(d\) is the number of binary digits of \(m\) for \(m>0\). Scanning the seed region visits all ordered pairs with \(0\le a\le b\lt S\), so this stage costs \(O(S^2)\) time. If \(T\) denotes the total number of forward orbit steps whose second component remains at most \(n\), the full running time is

$O(S^2+T).$

The algorithm stores only a constant number of integers while scanning and walking the orbits, so its auxiliary memory usage is

$O(1).$

Footnotes and References

  1. Problem page: https://projecteuler.net/problem=878
  2. Carry-less product: Wikipedia - Carry-less product
  3. Exclusive OR: Wikipedia - Exclusive or
  4. Finite fields: Wikipedia - Finite field
  5. Recurrence relations: Wikipedia - Recurrence relation

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

Previous: Problem 877 · All Project Euler solutions · Next: Problem 879

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! 🧮