1. Formal Invariant — Bézout's Identity, CRT & Linear Sieve Uniqueness
Invariant 1: Extended Euclidean Algorithm (Bézout's Identity)
For any integers $a, b$, ext_gcd(a, b, x, y) maintains $a x + b y = g = \gcd(a, b)$. From the recursive step $(b, a \bmod b)$ returning $b x_1 + (a - \lfloor a/b \rfloor b) y_1 = g$, rearranging terms yields the exact integer transition:
$$x = y_1, \qquad y = x_1 - \lfloor a / b \rfloor \cdot y_1$$
Invariant 2: Generalized Chinese Remainder Theorem (Non-Coprime Moduli)
Two congruences $X \equiv r_1 \pmod{m_1}$ and $X \equiv r_2 \pmod{m_2}$ have a common solution if and only if $r_1 \equiv r_2 \pmod{g}$ where $g = \gcd(m_1, m_2)$, in which case the merged congruence is unique modulo $\text{lcm}(m_1, m_2) = \frac{m_1}{g} m_2$.
Invariant 3: Euler's Linear Sieve
Every composite $x = p_{\min} \cdot i$ has a unique smallest prime factor $p_{\min} = \text{spf}[x]$. In the inner loop over primes $p \le \text{spf}[i]$, breaking immediately as soon as i % p == 0 guarantees that every composite $x \le N$ is marked exactly once (by $p = \text{spf}[x]$), achieving true $O(N)$ time!
2. Chinese Remainder Theorem Congruence Merging Pipeline
and X = r2 (mod m2)"] --> ExtGCD["Run ext_gcd(m1, m2, p, q)
m1*p + m2*q = g"] ExtGCD --> Check{"Is (r2 - r1) % g == 0 ?"} Check -->|"No"| NoSol["Contradiction: Return {-1, -1}"] Check -->|"Yes"| Combine["step = m2 / g
k = ((r2 - r1) / g % step * p) % step
r_new = r1 + m1 * k
m_new = m1 * (m2 / g)"]
3. Linear-Time Modular Inverse Table in $O(N)$
When precomputing factorials and inverse factorials modulo a prime $M$ up to $N$, we can compute all modular inverses $\text{inv}[1..N]$ in $O(N)$ total time without a single log M binary exponentiation! Writing $M = q \cdot i + r$ where $q = \lfloor M / i \rfloor$ and $r = M \bmod i < i$, we have $q \cdot i + r \equiv 0 \pmod M$. Multiplying by $i^{-1} r^{-1}$ gives the one-line recurrence:
$$\text{inv}[i] = \left( M - \lfloor M / i \rfloor \right) \cdot \text{inv}[M \bmod i] \bmod M$$
4. Hardware & Cache Reality
Bitset Wheel Sieve vs Linear SPF Sieve
While Euler's Linear Sieve does $O(N)$ arithmetic operations vs $O(N \log \log N)$ for Eratosthenes, storing int spf[N] requires $4N$ bytes of RAM ($400\text{ MB}$ for $N = 10^8$), which spills out of L3 cache. If you only need primality bits (not factorization), an odd-only bit-packed std::bitset Sieve of Eratosthenes uses $N / 16$ bytes ($6.25\text{ MB}$ for $N = 10^8$), fitting completely inside L3 cache and running $3\times$ faster in wall-clock time!
5. Complexity Matrix
Step through Extended GCD Bézout coefficients ax + by = gcd(a,b), Miller-Rabin modular squaring witness chains, and Pollard Rho cycle detection gcd(|x-y|, n) > 1.
Invariant & State
| Number Theory Algorithm | Time Complexity | Space Complexity | Applicability |
|---|---|---|---|
Extended Euclidean Algorithm (ext_gcd) |
O(log(min(a, b))) | O(1) | Bézout coefficients, inverse for any coprime $m$ |
| Fermat Inverse ($a^{M-2} \bmod M$) | O(log M) | O(1) | Prime modulus $M$ only |
| All Inverses $1..N \pmod M$ Recurrence | O(N) | O(N) | Prime modulus $M > N$ |
| Sieve of Eratosthenes | O(N log log N) | O(N) bits | Fastest cache-resident primality table |
Euler's Linear Sieve (spf[x], $\varphi(x)$, $\mu(x)$) |
O(N) | O(N) ints | $O(\log x)$ factorization & multiplicative functions |
6. Production-Grade C++20 Implementation
#include <vector>
#include <cstdint>
#include <utility>
// Iterative Extended GCD: returns g = gcd(a, b) and sets x, y such that a*x + b*y = g
int64_t ext_gcd(int64_t a, int64_t b, int64_t& x, int64_t& y) {
x = 1; y = 0;
int64_t x1 = 0, y1 = 1, a1 = a, b1 = b;
while (b1 != 0) {
int64_t q = a1 / b1;
std::tie(x, x1) = std::make_tuple(x1, x - q * x1);
std::tie(y, y1) = std::make_tuple(y1, y - q * y1);
std::tie(a1, b1) = std::make_tuple(b1, a1 - q * b1);
}
return a1;
}
// Modular inverse of a modulo m (works for ANY m where gcd(a, m) == 1)
int64_t mod_inv(int64_t a, int64_t m) {
int64_t x, y;
int64_t g = ext_gcd(a, m, x, y);
if (g != 1) return -1; // Inverse does not exist
return (x % m + m) % m;
}
// Generalized Chinese Remainder Theorem for potentially non-coprime moduli:
// Returns {r, lcm} such that X = r (mod lcm), or {-1, -1} if no solution exists
std::pair<int64_t, int64_t> crt_merge(int64_t r1, int64_t m1, int64_t r2, int64_t m2) {
int64_t p, q;
int64_t g = ext_gcd(m1, m2, p, q);
if ((r2 - r1) % g != 0) return {-1, -1};
int64_t step = m2 / g;
__int128 diff = (r2 - r1) / g;
int64_t k = static_cast<int64_t>((diff % step * (p % step)) % step);
if (k < 0) k += step;
int64_t lcm = m1 * step;
int64_t r = static_cast<int64_t>((r1 + static_cast<__int128>(m1) * k) % lcm);
return {r, lcm};
}
// Euler's O(N) Linear Sieve computing smallest prime factor spf[x] and totient phi[x]
struct LinearSieve {
std::vector<int> spf;
std::vector<int> phi;
std::vector<int> primes;
explicit LinearSieve(int n) : spf(n + 1, 0), phi(n + 1, 0) {
phi[1] = 1;
for (int i = 2; i <= n; ++i) {
if (spf[i] == 0) {
spf[i] = i;
phi[i] = i - 1;
primes.push_back(i);
}
for (int p : primes) {
if (p > spf[i] || 1LL * i * p > n) break;
spf[i * p] = p;
if (i % p == 0) {
phi[i * p] = phi[i] * p; // p^2 divides i*p
break;
} else {
phi[i * p] = phi[i] * (p - 1); // gcd(i, p) == 1
}
}
}
}
};
Production Pitfall — Using $a^{m-2} \bmod m$ When $m$ Is Composite
Fermat's Little Theorem ($a^{m-2} \equiv a^{-1} \pmod m$) holds only when $m$ is prime! If $m$ is composite (such as $m = 10^9$ or $m = 2^{64}$), $a^{m-2} \bmod m$ produces garbage. For composite $m$ with $\gcd(a, m) = 1$, either use Euler's Totient Theorem $a^{\varphi(m)-1} \pmod m$ or—better yet—call ext_gcd(a, m, x, y), which works in $O(\log m)$ time without needing to factor $m$ to find $\varphi(m)$!