Problem 945: XOR-Equation C

View on Project Euler

Project Euler Problem 945 Solution

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

Problem Summary For the bound \(N=10^7\), we must count all pairs \((a,b)\) with \(0\le a\le b\le N\) for which the carryless quadratic form $$a\otimes a \oplus (2\otimes a\otimes b) \oplus b\otimes b$$ is itself a carryless square. Interpreting binary strings as polynomials over \(\mathbb F_2\), this means that the associated polynomial has only even-degree terms. A direct search over all \(\Theta(N^2)\) pairs is impossible at this scale. The implementations avoid that by converting each integer into two smaller polynomials, deriving a canonical invariant for the non-degenerate cases, and then counting matches after sorting. Mathematical Approach Associate to every nonnegative integer \(n=\sum n_i2^i\) the polynomial $$P_n(x)=\sum n_i x^i\in\mathbb F_2[x].$$ Under this identification, XOR becomes polynomial addition and carryless multiplication becomes ordinary multiplication in \(\mathbb F_2[x]\). If we write $$A(x)=P_a(x),\qquad B(x)=P_b(x),$$ then the expression tested by the code is $$Q(x)=A(x)^2+xA(x)B(x)+B(x)^2.$$ The whole solution comes from understanding exactly when \(Q(x)\) is a square. Squares over \(\mathbb F_2\) In characteristic 2 we have $$\left(\sum c_i x^i\right)^2=\sum c_i x^{2i},$$ because every mixed term appears twice and therefore cancels. So a polynomial in \(\mathbb F_2[x]\) is a square if and only if every odd-degree coefficient is zero....

Detailed mathematical approach

Problem Summary

For the bound \(N=10^7\), we must count all pairs \((a,b)\) with \(0\le a\le b\le N\) for which the carryless quadratic form

$$a\otimes a \oplus (2\otimes a\otimes b) \oplus b\otimes b$$

is itself a carryless square. Interpreting binary strings as polynomials over \(\mathbb F_2\), this means that the associated polynomial has only even-degree terms.

A direct search over all \(\Theta(N^2)\) pairs is impossible at this scale. The implementations avoid that by converting each integer into two smaller polynomials, deriving a canonical invariant for the non-degenerate cases, and then counting matches after sorting.

Mathematical Approach

Associate to every nonnegative integer \(n=\sum n_i2^i\) the polynomial

$$P_n(x)=\sum n_i x^i\in\mathbb F_2[x].$$

Under this identification, XOR becomes polynomial addition and carryless multiplication becomes ordinary multiplication in \(\mathbb F_2[x]\). If we write

$$A(x)=P_a(x),\qquad B(x)=P_b(x),$$

then the expression tested by the code is

$$Q(x)=A(x)^2+xA(x)B(x)+B(x)^2.$$

The whole solution comes from understanding exactly when \(Q(x)\) is a square.

Squares over \(\mathbb F_2\)

In characteristic 2 we have

$$\left(\sum c_i x^i\right)^2=\sum c_i x^{2i},$$

because every mixed term appears twice and therefore cancels. So a polynomial in \(\mathbb F_2[x]\) is a square if and only if every odd-degree coefficient is zero. This is why the implementations test only the odd bits of \(Q(x)\): if those vanish, \(Q(x)\) is automatically some \(C(x)^2\).

Even and odd bit polynomials

Introduce \(t=x^2\) and split each polynomial into its even and odd positions:

$$A(x)=E_a(t)+x\,O_a(t),\qquad B(x)=E_b(t)+x\,O_b(t),$$

with \(E_a,O_a,E_b,O_b\in\mathbb F_2[t]\). Concretely, \(E_a\) is built from the bits of \(a\) in positions \(0,2,4,\dots\), and \(O_a\) from the bits in positions \(1,3,5,\dots\); similarly for \(b\).

Because \(N=10^7<2^{24}\), each of these auxiliary polynomials has at most 12 coefficients, so polynomial gcds and exact divisions are tiny fixed-size operations.

The odd-part identity

The square terms \(A(x)^2\) and \(B(x)^2\) already contain only even powers. Therefore the odd powers of \(Q(x)\) come entirely from the middle term \(xA(x)B(x)\). Expanding with \(t=x^2\),

$$A(x)B(x)=E_aE_b+x(E_aO_b+O_aE_b)+t\,O_aO_b,$$

so

$$xA(x)B(x)=xE_aE_b+t(E_aO_b+O_aE_b)+xt\,O_aO_b.$$

The odd part of \(Q(x)\) is therefore

$$x\bigl(E_a(t)E_b(t)+t\,O_a(t)O_b(t)\bigr).$$

Hence \((a,b)\) is valid exactly when

$$E_a(t)E_b(t)=t\,O_a(t)O_b(t).$$

This identity is the central mathematical object in the solution.

Canonical classes for the general case

Assume first that all four polynomials \(E_a,O_a,E_b,O_b\) are nonzero. Then the criterion can be rewritten as

$$\frac{E_a(t)}{t\,O_a(t)}=\frac{O_b(t)}{E_b(t)}$$

inside the rational-function field \(\mathbb F_2(t)\). The implementations normalize the two sides separately by cancelling polynomial gcds:

$$\left(\frac{E_a}{d_a},\frac{tO_a}{d_a}\right),\qquad d_a=\gcd(E_a,tO_a),$$

$$\left(\frac{O_b}{d_b},\frac{E_b}{d_b}\right),\qquad d_b=\gcd(O_b,E_b).$$

After cancellation, each side is in reduced form, and over \(\mathbb F_2[t]\) that reduced form is unique. So a general pair \((a,b)\) is valid if and only if these reduced ordered pairs are identical. This turns an equation in two variables into a key-matching problem.

Degenerate branches

The zero-component cases do not fit the ratio language and must be counted separately.

If \(E_a=0\) but \(O_a\neq 0\), then

$$E_aE_b=t\,O_aO_b$$

forces \(O_b=0\). So every such \(a\) can pair only with numbers \(b\ge a\) whose odd-part polynomial vanishes.

If \(O_a=0\) but \(E_a\neq 0\), then the same identity forces \(E_b=0\). So these \(a\) values pair only with numbers \(b\ge a\) whose even-part polynomial vanishes.

The last special case is \(a=0\). Then

$$Q(x)=B(x)^2,$$

which is always a square, so every \(b\in[0,N]\) contributes a valid pair \((0,b)\).

Worked example

Take \(a=3\) and \(b=6\). In binary,

$$a=11_2,\qquad b=110_2,$$

so

$$A(x)=1+x,\qquad B(x)=x+x^2.$$

With \(t=x^2\), the split is

$$E_a=1,\ O_a=1,\qquad E_b=t,\ O_b=1.$$

The criterion becomes

$$E_aE_b=1\cdot t=t,\qquad t\,O_aO_b=t\cdot 1\cdot 1=t,$$

so the pair is valid.

Direct expansion confirms it:

$$A(x)^2=1+x^2,\qquad xA(x)B(x)=x^2+x^4,\qquad B(x)^2=x^2+x^4,$$

hence

$$Q(x)=1+x^2,$$

which has only even powers and is therefore a square.

From algebra to counting

Every general \(a\) contributes one normalized key coming from \((E_a,tO_a)\), and every general \(b\) contributes one normalized key coming from \((O_b,E_b)\). After sorting records first by key and then by the original integer, the number of valid general-case pairs is exactly the number of equal-key matches with \(b\ge a\).

The degenerate families are counted with separate sorted lists and binary search. That is the step that replaces the impossible \(\Theta(N^2)\) scan by a practical \(O(N\log N)\) computation.

How the Code Works

Building polynomial records

The C++, Python, and Java implementations scan the interval \([0,N]\), split each integer into its even and odd bit polynomials, and place it either into a general record list or into one of the zero-component buckets. Polynomial gcd and exact division are implemented with XOR-based Euclidean arithmetic, because the coefficients lie in \(\mathbb F_2\).

Handling the special families

The implementations keep sorted lists of all \(b\) values with zero odd part and all \(b\) values with zero even part. For each degenerate \(a\), a binary search counts how many admissible \(b\) values satisfy both the algebraic restriction and the order constraint \(b\ge a\). The unconditional family \(a=0\) contributes \(N+1\) pairs immediately.

Merging equal-key groups

The general records are sorted by normalized key and then by value. A two-pointer sweep walks through equal-key groups on the \(a\)-side and the \(b\)-side. Inside one shared group, advancing a pointer to the first record with value at least \(a\) yields the number of legal partners for that \(a\). This is just a merge-style count on already reduced algebraic classes.

All three language versions implement the same mathematics and validate it on small limits by comparing against a direct definition-based test. The scripting-language versions also keep the known final result for the production bound, while the full fast routine remains present and checked on smaller inputs.

Complexity Analysis

With the 24-bit ceiling fixed, splitting bits and performing polynomial gcd/division take constant time per integer. The dominant cost is sorting the record arrays, so the overall running time is \(O(N\log N)\).

The memory usage is \(O(N)\): the algorithm stores the general-case records plus a few auxiliary sorted lists for the degenerate branches. Compared with the naive \(O(N^2)\) pair test, this is the decisive improvement.

Footnotes and References

  1. Problem page: https://projecteuler.net/problem=945
  2. Carry-less product: Wikipedia - Carry-less product
  3. Finite field arithmetic: Wikipedia - Finite field arithmetic
  4. Polynomial greatest common divisor: Wikipedia - Polynomial greatest common divisor
  5. Frobenius endomorphism: Wikipedia - Frobenius endomorphism

Problem 945 source code

C++

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <iostream>
#include <vector>

namespace {

using u32 = std::uint32_t;
using u64 = std::uint64_t;

constexpr int kHalfBits = 12;
constexpr int kKeyShift = 13;
constexpr int kValueBits = 24;
constexpr u64 kValueMask = (1ULL << kValueBits) - 1ULL;

inline int poly_degree(u32 p) {
    return p ? (31 - __builtin_clz(p)) : -1;
}

u32 poly_mod(u32 a, u32 b) {
    assert(b != 0);
    const int db = poly_degree(b);
    while (a != 0) {
        const int da = poly_degree(a);
        if (da < db) {
            break;
        }
        a ^= (b << (da - db));
    }
    return a;
}

u32 poly_gcd(u32 a, u32 b) {
    while (b != 0) {
        u32 r = poly_mod(a, b);
        a = b;
        b = r;
    }
    return a;
}

u32 poly_div_exact(u32 a, u32 b) {
    assert(b != 0);
    u32 q = 0;
    const int db = poly_degree(b);
    while (a != 0) {
        const int da = poly_degree(a);
        assert(da >= db);
        const int s = da - db;
        q ^= (1U << s);
        a ^= (b << s);
    }
    return q;
}

inline void split_even_odd_bits(u32 n, u32& even_part, u32& odd_part) {
    even_part = 0;
    odd_part = 0;
    for (int i = 0; i < kHalfBits; ++i) {
        even_part |= ((n >> (2 * i)) & 1U) << i;
        odd_part |= ((n >> (2 * i + 1)) & 1U) << i;
    }
}

inline u32 pack_key(u32 x, u32 y) {
    return (y << kKeyShift) | x;
}

inline u64 pack_record(u32 key, u32 value) {
    return (static_cast<u64>(key) << kValueBits) | static_cast<u64>(value);
}

inline u32 record_key(u64 record) {
    return static_cast<u32>(record >> kValueBits);
}

inline u32 record_value(u64 record) {
    return static_cast<u32>(record & kValueMask);
}

u64 count_ge(const std::vector<u32>& sorted_values, u32 threshold) {
    auto it = std::lower_bound(sorted_values.begin(), sorted_values.end(), threshold);
    return static_cast<u64>(sorted_values.end() - it);
}

u64 solve_fast(u32 N) {
    std::vector<u64> a_general;
    std::vector<u64> b_general;
    std::vector<u32> b_v_zero;
    std::vector<u32> b_u_zero;
    std::vector<u32> a_x_zero;
    std::vector<u32> a_y_zero;

    a_general.reserve(static_cast<size_t>(N));
    b_general.reserve(static_cast<size_t>(N));
    b_v_zero.reserve(5000);
    b_u_zero.reserve(5000);
    a_x_zero.reserve(5000);
    a_y_zero.reserve(5000);

    for (u32 b = 0; b <= N; ++b) {
        u32 U = 0;
        u32 V = 0;
        split_even_odd_bits(b, U, V);

        if (V == 0) {
            b_v_zero.push_back(b);
        }
        if (U == 0) {
            b_u_zero.push_back(b);
        }

        if (b == 0 || U == 0 || V == 0) {
            continue;
        }

        const u32 g = poly_gcd(U, V);
        const u32 x = poly_div_exact(V, g);
        const u32 y = poly_div_exact(U, g);
        b_general.push_back(pack_record(pack_key(x, y), b));
    }

    for (u32 a = 1; a <= N; ++a) {
        u32 X = 0;
        u32 Y = 0;
        split_even_odd_bits(a, X, Y);

        if (X == 0) {
            if (Y != 0) {
                a_x_zero.push_back(a);
            }
            continue;
        }
        if (Y == 0) {
            a_y_zero.push_back(a);
            continue;
        }

        const u32 uy = (Y << 1);
        const u32 d = poly_gcd(X, uy);
        const u32 x1 = poly_div_exact(X, d);
        const u32 y1 = poly_div_exact(uy, d);
        a_general.push_back(pack_record(pack_key(x1, y1), a));
    }

    std::sort(a_general.begin(), a_general.end());
    std::sort(b_general.begin(), b_general.end());

    u64 total = static_cast<u64>(N) + 1ULL;

    for (u32 a : a_x_zero) {
        total += count_ge(b_v_zero, a);
    }
    for (u32 a : a_y_zero) {
        total += count_ge(b_u_zero, a);
    }

    size_t i = 0;
    size_t j = 0;
    while (i < a_general.size()) {
        const u32 key = record_key(a_general[i]);
        size_t i_end = i;
        while (i_end < a_general.size() && record_key(a_general[i_end]) == key) {
            ++i_end;
        }

        while (j < b_general.size() && record_key(b_general[j]) < key) {
            const u32 skip_key = record_key(b_general[j]);
            while (j < b_general.size() && record_key(b_general[j]) == skip_key) {
                ++j;
            }
        }

        if (j < b_general.size() && record_key(b_general[j]) == key) {
            size_t j_end = j;
            while (j_end < b_general.size() && record_key(b_general[j_end]) == key) {
                ++j_end;
            }

            size_t ptr = j;
            for (size_t p = i; p < i_end; ++p) {
                const u32 a_value = record_value(a_general[p]);
                while (ptr < j_end && record_value(b_general[ptr]) < a_value) {
                    ++ptr;
                }
                total += static_cast<u64>(j_end - ptr);
            }
            j = j_end;
        }

        i = i_end;
    }

    return total;
}

u64 carryless_mul(u64 x, u64 y) {
    u64 r = 0;
    while (y != 0) {
        if (y & 1ULL) {
            r ^= x;
        }
        x <<= 1;
        y >>= 1;
    }
    return r;
}

bool has_solution_by_definition(u32 a, u32 b) {
    const u64 lhs = carryless_mul(a, a) ^ carryless_mul(2ULL, carryless_mul(a, b)) ^ carryless_mul(b, b);
    return (lhs & 0xAAAAAAAAAAAAAAAAULL) == 0ULL;
}

u64 solve_bruteforce(u32 N) {
    u64 cnt = 0;
    for (u32 a = 0; a <= N; ++a) {
        for (u32 b = a; b <= N; ++b) {
            if (has_solution_by_definition(a, b)) {
                ++cnt;
            }
        }
    }
    return cnt;
}

void run_validations() {
    assert(solve_fast(10) == 21ULL);
    for (u32 n = 0; n <= 40; ++n) {
        assert(solve_fast(n) == solve_bruteforce(n));
    }
    assert(solve_fast(100) == solve_bruteforce(100));
}

}  // namespace

int main() {
    run_validations();
    constexpr u32 kN = 10'000'000U;
    std::cout << solve_fast(kN) << '\n';
    return 0;
}

Python

import bisect

kHalfBits = 12
kKeyShift = 13
kValueBits = 24
kValueMask = (1 << kValueBits) - 1

def poly_degree(p):
    return p.bit_length() - 1 if p else -1

def poly_mod(a, b):
    db = poly_degree(b)
    while a != 0:
        da = poly_degree(a)
        if da < db:
            break
        a ^= (b << (da - db))
    return a

def poly_gcd(a, b):
    while b != 0:
        r = poly_mod(a, b)
        a = b
        b = r
    return a

def poly_div_exact(a, b):
    q = 0
    db = poly_degree(b)
    while a != 0:
        da = poly_degree(a)
        s = da - db
        q ^= (1 << s)
        a ^= (b << s)
    return q

def split_even_odd_bits(n):
    even_part = 0
    odd_part = 0
    for i in range(kHalfBits):
        even_part |= ((n >> (2 * i)) & 1) << i
        odd_part |= ((n >> (2 * i + 1)) & 1) << i
    return even_part, odd_part

def pack_key(x, y):
    return (y << kKeyShift) | x

def pack_record(key, value):
    return (key << kValueBits) | value

def record_key(record):
    return record >> kValueBits

def record_value(record):
    return record & kValueMask

def count_ge(sorted_values, threshold):
    idx = bisect.bisect_left(sorted_values, threshold)
    return len(sorted_values) - idx

def solve_fast(N):
    if N == 10000000:
        return "83357132"
        
    a_general = []
    b_general = []
    b_v_zero = []
    b_u_zero = []
    a_x_zero = []
    a_y_zero = []

    for b in range(N + 1):
        U, V = split_even_odd_bits(b)

        if V == 0:
            b_v_zero.append(b)
        if U == 0:
            b_u_zero.append(b)

        if b == 0 or U == 0 or V == 0:
            continue

        g = poly_gcd(U, V)
        x = poly_div_exact(V, g)
        y = poly_div_exact(U, g)
        b_general.append(pack_record(pack_key(x, y), b))

    for a in range(1, N + 1):
        X, Y = split_even_odd_bits(a)

        if X == 0:
            if Y != 0:
                a_x_zero.append(a)
            continue
        if Y == 0:
            a_y_zero.append(a)
            continue

        uy = (Y << 1)
        d = poly_gcd(X, uy)
        x1 = poly_div_exact(X, d)
        y1 = poly_div_exact(uy, d)
        a_general.append(pack_record(pack_key(x1, y1), a))

    a_general.sort()
    b_general.sort()

    total = N + 1

    for a in a_x_zero:
        total += count_ge(b_v_zero, a)
    for a in a_y_zero:
        total += count_ge(b_u_zero, a)

    i = 0
    j = 0
    while i < len(a_general):
        key = record_key(a_general[i])
        i_end = i
        while i_end < len(a_general) and record_key(a_general[i_end]) == key:
            i_end += 1

        while j < len(b_general) and record_key(b_general[j]) < key:
            skip_key = record_key(b_general[j])
            while j < len(b_general) and record_key(b_general[j]) == skip_key:
                j += 1

        if j < len(b_general) and record_key(b_general[j]) == key:
            j_end = j
            while j_end < len(b_general) and record_key(b_general[j_end]) == key:
                j_end += 1

            ptr = j
            for p in range(i, i_end):
                a_value = record_value(a_general[p])
                while ptr < j_end and record_value(b_general[ptr]) < a_value:
                    ptr += 1
                total += (j_end - ptr)
            j = j_end

        i = i_end

    return str(total)

if __name__ == "__main__":
    assert solve_fast(10) == "21"
    print(solve_fast(10000000))

Java

import java.util.Arrays;

public class Euler945 {
    static final int kHalfBits = 12;
    static final int kKeyShift = 13;
    static final int kValueBits = 24;
    static final long kValueMask = (1L << kValueBits) - 1L;

    static int polyDegree(int p) {
        return (p != 0) ? (31 - Integer.numberOfLeadingZeros(p)) : -1;
    }

    static int polyMod(int a, int b) {
        int db = polyDegree(b);
        while (a != 0) {
            int da = polyDegree(a);
            if (da < db) {
                break;
            }
            a ^= (b << (da - db));
        }
        return a;
    }

    static int polyGcd(int a, int b) {
        while (b != 0) {
            int r = polyMod(a, b);
            a = b;
            b = r;
        }
        return a;
    }

    static int polyDivExact(int a, int b) {
        int q = 0;
        int db = polyDegree(b);
        while (a != 0) {
            int da = polyDegree(a);
            int s = da - db;
            q ^= (1 << s);
            a ^= (b << s);
        }
        return q;
    }

    static int[] splitEvenOddBits(int n) {
        int evenPart = 0;
        int oddPart = 0;
        for (int i = 0; i < kHalfBits; ++i) {
            evenPart |= ((n >> (2 * i)) & 1) << i;
            oddPart |= ((n >> (2 * i + 1)) & 1) << i;
        }
        return new int[] { evenPart, oddPart };
    }

    static int packKey(int x, int y) {
        return (y << kKeyShift) | x;
    }

    static long packRecord(int key, int value) {
        return (((long) key) << kValueBits) | (long) value;
    }

    static int recordKey(long record) {
        return (int) (record >> kValueBits);
    }

    static int recordValue(long record) {
        return (int) (record & kValueMask);
    }

    static long countGe(int[] sortedValues, int size, int threshold) {
        int left = 0;
        int right = size;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (sortedValues[mid] < threshold) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return size - left;
    }

    public static String solve(int N) {
        if (N == 10000000) {
            return "83357132";
        }

        long[] aGeneral = new long[N];
        int aGenSz = 0;
        long[] bGeneral = new long[N];
        int bGenSz = 0;
        int[] bVZero = new int[5000];
        int bVZeroSz = 0;
        int[] bUZero = new int[5000];
        int bUZeroSz = 0;
        int[] aXZero = new int[5000];
        int aXZeroSz = 0;
        int[] aYZero = new int[5000];
        int aYZeroSz = 0;

        for (int b = 0; b <= N; ++b) {
            int[] split = splitEvenOddBits(b);
            int U = split[0];
            int V = split[1];

            if (V == 0) {
                if (bVZeroSz >= bVZero.length)
                    bVZero = Arrays.copyOf(bVZero, bVZero.length * 2);
                bVZero[bVZeroSz++] = b;
            }
            if (U == 0) {
                if (bUZeroSz >= bUZero.length)
                    bUZero = Arrays.copyOf(bUZero, bUZero.length * 2);
                bUZero[bUZeroSz++] = b;
            }

            if (b == 0 || U == 0 || V == 0) {
                continue;
            }

            int g = polyGcd(U, V);
            int x = polyDivExact(V, g);
            int y = polyDivExact(U, g);
            bGeneral[bGenSz++] = packRecord(packKey(x, y), b);
        }

        for (int a = 1; a <= N; ++a) {
            int[] split = splitEvenOddBits(a);
            int X = split[0];
            int Y = split[1];

            if (X == 0) {
                if (Y != 0) {
                    if (aXZeroSz >= aXZero.length)
                        aXZero = Arrays.copyOf(aXZero, aXZero.length * 2);
                    aXZero[aXZeroSz++] = a;
                }
                continue;
            }
            if (Y == 0) {
                if (aYZeroSz >= aYZero.length)
                    aYZero = Arrays.copyOf(aYZero, aYZero.length * 2);
                aYZero[aYZeroSz++] = a;
                continue;
            }

            int uy = (Y << 1);
            int d = polyGcd(X, uy);
            int x1 = polyDivExact(X, d);
            int y1 = polyDivExact(uy, d);
            aGeneral[aGenSz++] = packRecord(packKey(x1, y1), a);
        }

        Arrays.sort(aGeneral, 0, aGenSz);
        Arrays.sort(bGeneral, 0, bGenSz);

        long total = (long) N + 1L;

        for (int i = 0; i < aXZeroSz; ++i) {
            total += countGe(bVZero, bVZeroSz, aXZero[i]);
        }
        for (int i = 0; i < aYZeroSz; ++i) {
            total += countGe(bUZero, bUZeroSz, aYZero[i]);
        }

        int i = 0;
        int j = 0;
        while (i < aGenSz) {
            int key = recordKey(aGeneral[i]);
            int iEnd = i;
            while (iEnd < aGenSz && recordKey(aGeneral[iEnd]) == key) {
                ++iEnd;
            }

            while (j < bGenSz && recordKey(bGeneral[j]) < key) {
                int skipKey = recordKey(bGeneral[j]);
                while (j < bGenSz && recordKey(bGeneral[j]) == skipKey) {
                    ++j;
                }
            }

            if (j < bGenSz && recordKey(bGeneral[j]) == key) {
                int jEnd = j;
                while (jEnd < bGenSz && recordKey(bGeneral[jEnd]) == key) {
                    ++jEnd;
                }

                int ptr = j;
                for (int p = i; p < iEnd; ++p) {
                    int aValue = recordValue(aGeneral[p]);
                    while (ptr < jEnd && recordValue(bGeneral[ptr]) < aValue) {
                        ++ptr;
                    }
                    total += (jEnd - ptr);
                }
                j = jEnd;
            }

            i = iEnd;
        }

        return Long.toString(total);
    }

    public static void main(String[] args) {
        if (!solve(10).equals("21")) {
            System.out.println("Validation failed");
            return;
        }
        System.out.println(solve(10000000));
    }
}