All Euler problems
Project Euler

Modular Inverses

For a positive integer n, define I(n) as the largest positive integer m < n - 1 such that m^2 equiv 1 (mod n). If no such m exists, set I(n) = 1. Known values: I(7) = 1, I(15) = 11, I(100) = 51. Co...

Source sync Apr 19, 2026
Problem #0451
Level Level 11
Solved By 1,718
Languages C++, Python
Answer 153651073760956
Length 333 words
modular_arithmeticnumber_theorylinear_algebra

Problem Statement

This archive keeps the full statement, math, and original media on the page.

Consider the number .
There are eight positive numbers less than which are coprime to : .
The modular inverses of these numbers modulo are:
because





Let be the largest positive number smaller than such that the modular inverse of modulo equals itself.
So .
Also and .

Find for .

Problem 451: Modular Inverses

Mathematical Foundation

Definition. A number mm with 1m<n1 \le m < n and gcd(m,n)=1\gcd(m, n) = 1 is a self-inverse modulo nn if m21(modn)m^2 \equiv 1 \pmod{n}.

Theorem 1 (CRT factorization of self-inverses). Let n=p1a1p2a2pkakn = p_1^{a_1} p_2^{a_2} \cdots p_k^{a_k}. Then

m21(modn)    m21(modpiai) for all i=1,,k.m^2 \equiv 1 \pmod{n} \iff m^2 \equiv 1 \pmod{p_i^{a_i}} \text{ for all } i = 1, \ldots, k.

Proof. This is an immediate consequence of the Chinese Remainder Theorem: Z/nZi=1kZ/piaiZ\mathbb{Z}/n\mathbb{Z} \cong \prod_{i=1}^{k} \mathbb{Z}/p_i^{a_i}\mathbb{Z}, and the equation m210m^2 - 1 \equiv 0 holds in the product ring if and only if it holds in each factor. \square

Lemma 1 (Solutions modulo prime powers). The solutions of x21(modpa)x^2 \equiv 1 \pmod{p^a} are:

  • For pp odd: exactly 2 solutions, x±1(modpa)x \equiv \pm 1 \pmod{p^a}.
  • For p=2p = 2, a=1a = 1: 1 solution, x=1x = 1.
  • For p=2p = 2, a=2a = 2: 2 solutions, x{1,3}x \in \{1, 3\}.
  • For p=2p = 2, a3a \ge 3: 4 solutions, x{1,  2a11,  2a1+1,  2a1}x \in \{1,\; 2^{a-1}-1,\; 2^{a-1}+1,\; 2^a-1\}.

Proof. For odd pp, the group (Z/paZ)(\mathbb{Z}/p^a\mathbb{Z})^* is cyclic of order φ(pa)=pa1(p1)\varphi(p^a) = p^{a-1}(p-1). In a cyclic group, the equation x2=ex^2 = e has at most 2 solutions. Since x=±1x = \pm 1 are always solutions, there are exactly 2.

For p=2p = 2 and a3a \ge 3, we have (Z/2aZ)Z/2×Z/2a2(\mathbb{Z}/2^a\mathbb{Z})^* \cong \mathbb{Z}/2 \times \mathbb{Z}/2^{a-2}. The equation x2=1x^2 = 1 in a product of cyclic groups Z/2×Z/2a2\mathbb{Z}/2 \times \mathbb{Z}/2^{a-2} has 2×2=42 \times 2 = 4 solutions (the identity and the unique element of order 2 in each factor). Direct verification:

121,(2a11)2=22a22a+11,(2a1+1)2=22a2+2a+11,(2a1)2=22a2a+1+11(mod2a).1^2 \equiv 1, \quad (2^{a-1}-1)^2 = 2^{2a-2} - 2^a + 1 \equiv 1, \quad (2^{a-1}+1)^2 = 2^{2a-2} + 2^a + 1 \equiv 1, \quad (2^a - 1)^2 = 2^{2a} - 2^{a+1} + 1 \equiv 1 \pmod{2^a}.

The cases a=1a = 1 and a=2a = 2 are verified by exhaustion. \square

Corollary. The total number of self-inverses modulo n=piain = \prod p_i^{a_i} is i=1ks(piai)\prod_{i=1}^{k} s(p_i^{a_i}), where s(pa)s(p^a) denotes the count from Lemma 1.

Theorem 2 (Characterization of I(n)I(n)). Given the complete set of self-inverses modulo nn (constructed via CRT from Theorem 1 and Lemma 1), I(n)I(n) equals the second-largest element of this set. The largest is always n1n - 1; if the only solutions are {1,n1}\{1, n-1\}, then I(n)=1I(n) = 1.

Proof. Note that m=n1m = n - 1 satisfies (n1)2=n22n+11(modn)(n-1)^2 = n^2 - 2n + 1 \equiv 1 \pmod{n}, so n1n-1 is always a self-inverse. By definition, I(n)I(n) is the largest self-inverse strictly less than n1n - 1. If no non-trivial self-inverse exists, the only solution below n1n-1 is m=1m = 1, giving I(n)=1I(n) = 1. \square

Editorial

Approach: Sieve-based CRT. For each n, we build up the set of self-inverses by processing one prime-power factor at a time. We store only the set of known self-inverses so far (from partial factorization). We sieve smallest prime factor spf[i] for 2 <= i <= N. Finally, return total.

Pseudocode

Sieve smallest prime factor spf[i] for 2 <= i <= N
total <- 0
For n = 3 to N:
Return total

Complexity Analysis

  • Time: O(NloglogN)O(N \log \log N) for the SPF sieve. For each nn, factoring via SPF takes O(logn)O(\log n). The CRT reconstruction processes ω(n)\omega(n) prime factors with up to 2ω(n)2^{\omega(n)} solutions; since ω(n)O(logn/loglogn)\omega(n) \le O(\log n / \log \log n) and on average ω(n)loglogn\omega(n) \approx \log \log n, the amortized cost per nn is small. Total: O(NloglogN)O(N \log \log N).
  • Space: O(N)O(N) for the SPF array.

Answer

153651073760956\boxed{153651073760956}

Code

Each problem page includes the exact C++ and Python source files from the local archive.

C++ project_euler/problem_451/solution.cpp
#include <bits/stdc++.h>
using namespace std;

/*
 * Project Euler Problem 451: Modular Inverses
 *
 * Find sum of I(n) for 3 <= n <= 2*10^7, where I(n) is the largest m < n-1
 * such that m^2 = 1 (mod n).
 *
 * Approach: Sieve-based. For each n, we store a vector of all self-inverse
 * residues. We build these up by processing prime powers: for each prime power
 * q = p^a, for each multiple n of q, we combine existing solutions with
 * solutions mod q via CRT.
 *
 * Answer: 153651073760956
 */

const int MAXN = 20000001;

// spf[i] = smallest prime factor of i
int spf[MAXN];

// For each n, store all self-inverse solutions found so far
// We use a vector of vectors; memory-intensive but manageable for 2*10^7
// Actually, we'll use a more memory-efficient approach:
// Process prime factors one at a time via SPF, combining CRT incrementally.

long long extgcd(long long a, long long b, long long &x, long long &y) {
    if (a == 0) { x = 0; y = 1; return b; }
    long long x1, y1;
    long long g = extgcd(b % a, a, x1, y1);
    x = y1 - (b / a) * x1;
    y = x1;
    return g;
}

// CRT: x = r1 mod m1, x = r2 mod m2, gcd(m1,m2)=1
// Returns x mod m1*m2
long long crt2(long long r1, long long m1, long long r2, long long m2) {
    long long x, y;
    extgcd(m1, m2, x, y);
    long long mod = m1 * m2;
    long long res = (r1 + m1 * ((r2 - r1) % m2 + m2) % m2 * x) % mod;
    // More carefully:
    long long diff = ((r2 - r1) % m2 + m2) % m2;
    res = r1 + m1 * (diff * ((x % m2 + m2) % m2) % m2);
    res = ((res % mod) + mod) % mod;
    return res;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    const int N = 20000000;

    // Sieve smallest prime factor
    for (int i = 0; i <= N; i++) spf[i] = i;
    for (int i = 2; (long long)i * i <= N; i++) {
        if (spf[i] == i) { // prime
            for (int j = i * i; j <= N; j += i) {
                if (spf[j] == j) spf[j] = i;
            }
        }
    }

    // For each n, factor using spf and compute all self-inverses via CRT
    // Then I(n) = second largest (largest < n-1)

    long long total = 0;

    for (int n = 3; n <= N; n++) {
        // Factor n
        int temp = n;
        // Collect prime powers
        // Use small static array since n <= 2*10^7 has at most ~8 prime factors
        int pp[10]; // prime powers p^a
        int npp = 0;

        while (temp > 1) {
            int p = spf[temp];
            int pa = 1;
            while (temp % p == 0) {
                pa *= p;
                temp /= p;
            }
            pp[npp++] = pa;
            // Store p as well for the 2-special case
            // We need to know if p==2 and what a is
            // Let's store both
            // Redo: store (p, pa) pairs
            pp[npp - 1] = pa; // we'll handle p==2 by checking pa
            // Actually let's use a struct approach
            // Simpler: just redo with separate arrays
            (void)p; // we'll fix below
        }

        // Redo factoring properly
        temp = n;
        npp = 0;
        int primes[10], pas[10];
        while (temp > 1) {
            int p = spf[temp];
            int pa = 1;
            while (temp % p == 0) {
                pa *= p;
                temp /= p;
            }
            primes[npp] = p;
            pas[npp] = pa;
            npp++;
        }

        // Build all self-inverse solutions via CRT
        // Start with solutions = {1} mod 1, current_mod = 1
        // Max solutions: 2^(npp) * possible factor of 2 for p=2, a>=3
        // Worst case about 2^9 = 512 solutions, but typically much fewer
        static long long sols[1024], new_sols[1024];
        int nsols = 1;
        sols[0] = 1;
        long long cur_mod = 1;

        for (int i = 0; i < npp; i++) {
            int p = primes[i];
            long long pa = pas[i];

            // Local solutions mod pa
            long long local_s[4];
            int nloc;
            if (p == 2) {
                if (pa == 1) {
                    local_s[0] = 1;
                    nloc = 1;
                } else if (pa == 2) {
                    // Actually for mod 4: 1^2=1, 3^2=9=1 mod 4. So {1, 3}.
                    local_s[0] = 1; local_s[1] = 3;
                    nloc = 2;
                } else {
                    // pa >= 8
                    long long half = pa / 2;
                    local_s[0] = 1;
                    local_s[1] = half - 1;
                    local_s[2] = half + 1;
                    local_s[3] = pa - 1;
                    nloc = 4;
                }
            } else {
                local_s[0] = 1;
                local_s[1] = pa - 1;
                nloc = 2;
            }

            // Combine via CRT
            int nnew = 0;
            for (int a = 0; a < nsols; a++) {
                for (int b = 0; b < nloc; b++) {
                    new_sols[nnew++] = crt2(sols[a], cur_mod, local_s[b], pa);
                }
            }

            cur_mod *= pa;
            nsols = nnew;
            memcpy(sols, new_sols, nsols * sizeof(long long));
        }

        // Find largest solution < n-1
        long long best = 1;
        for (int i = 0; i < nsols; i++) {
            long long s = sols[i];
            if (s > 1 && s < n - 1 && s > best) {
                best = s;
            }
        }

        total += best;
    }

    cout << total << endl;

    // Verification
    if (total == 153651073760956LL) {
        cout << "CORRECT!" << endl;
    } else {
        cout << "WRONG! Expected 153651073760956" << endl;
    }

    return 0;
}