DSA DSA In Depth
0%
lesson number-theoryextended-gcdchinese-remainder-theorem

Algorithmic Number Theory: Extended Euclidean Algorithm, Modular Inverse, CRT & Sieve of Eratosthenes

7 min read Module 6.4: Computational Geometry, Number Theory, Randomized & Approximation Algorithms Updated Oct 23, 2026
📄 Cheat Sheet
The Extended Euclidean Algorithm & Bezout Coefficients
GVSUmath The Extended Euclidean Algorithm & Bezout Coefficients Watch on YouTube ↗

Derives Bezout's Identity, modular inverses for non-prime moduli, and constructive Chinese Remainder Theorem reconstruction step-by-step.

0:00 / --:--

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

flowchart LR Cong["Merge X = r1 (mod m1)
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

Extended Euclidean Bézout, Miller-Rabin Witness & Pollard Rho Cycle
O(log³ n) Primality / O(n^(1/4)) Rho

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.

Step 1 / 7
Custom Input:
Timeline

Invariant & State

Pollard Rho Birthday Collision mod p: Even before x_i = x_j (mod n), x_i ≡ x_j (mod p) occurs in O(√p) ≤ O(n^(1/4)) steps, revealed by gcd(|x - y|, n) = p!
x₀=2 x₁=5 x₂=26 x₃=40 x₂=26 ≡ 5 (mod 7), x₄=53 ≡ 4, gcd(|26 - 5|, 91) = gcd(21, 91) = 7!
Polynomial Map
f(x) = (x² + 1) mod 91
Collision Check
gcd(|26 - 5|, 91) = 7
Factorization
91 = 7 × 13
[Step 1] Floyd tortoise-hare on f(x)=(x²+1) mod 91 found gcd(|x₂ - x₁|, 91) = 7 in 3 steps.
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)$!

Check yourself Question 1 of 4
Concept Check Core Invariant Check recall

In Euler's $O(N)$ Linear Sieve, what single line of code ensures that every composite integer $x \le N$ is crossed out **exactly once** (by its smallest prime factor $\text{spf}[x]$)?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

Why does the recurrence `inv[i] = (M - M / i) * inv[M % i] % M` compute all modular inverses $1..N$ modulo a prime $M > N$ in $O(N)$ total time?

Key Takeaways

The Extended Euclidean Algorithm finds Bezout coefficients a*x + b*y = gcd(a, b) in O(log(min(a, b))) time and computes modular inverses for any coprime modulus without factoring.
The Generalized Chinese Remainder Theorem merges X = r1 (mod m1) and X = r2 (mod m2) iff r1 == r2 (mod gcd(m1, m2)), yielding a unique residue modulo lcm(m1, m2).
All modular inverses 1..N modulo prime M can be precomputed in O(N) time via inv[i] = (M - M/i) * inv[M % i] % M.
Euler's Linear Sieve breaks its inner loop as soon as i % p == 0, visiting each composite x exactly once via its smallest prime factor spf[x] in O(N) time.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue