Number Theory Basics

intermediate30 min

What this covers

A handful of number-theory routines appear repeatedly in contest problems. None is long, but each has a cost that decides whether it fits the constraints. Knowing the cost is as important as knowing the code.

Divisors

To find every divisor of n, loop only to the square root. Divisors pair up: if d divides n, so does n / d, and one of the pair is always at most √n.

static List<Long> divisors(long n) {
    List<Long> result = new ArrayList<>();
    for (long d = 1; d * d <= n; d++) {
        if (n % d == 0) {
            result.add(d);
            if (d != n / d) {          // avoid adding the square root twice
                result.add(n / d);
            }
        }
    }
    return result;
}

This is O(√n). For n up to 1012 that is a million iterations — fine.

The condition d * d <= n rather than d <= Math.sqrt(n) avoids floating-point rounding at the boundary. The d != n / d guard prevents listing the square root twice for perfect squares.

How many divisors to expect

A number below 1018 has at most a few thousand divisors. So collecting them into a list is safe — you will not run out of memory. But the count of divisors grows irregularly, so never assume it is small in a complexity argument without checking.

Primality of a single number

Trial division to the square root, skipping even numbers after 2.

static boolean isPrime(long n) {
    if (n < 2) return false;
    if (n < 4) return true;            // 2 and 3
    if (n % 2 == 0) return false;
    for (long d = 3; d * d <= n; d += 2) {
        if (n % d == 0) return false;
    }
    return true;
}

O(√n). Good for n up to about 1014 as a one-off. If you need primality for many large numbers, use a Miller–Rabin test instead; for the ranges typical of contest problems, this suffices.

The early returns matter: n < 2 excludes 0, 1, and negatives, and 1 is not prime. That is a classic source of a wrong answer on the smallest test case.

Sieve of Eratosthenes

When you need every prime up to N, testing each individually is O(N√N). The sieve does it in roughly O(N log log N), which is effectively linear.

static boolean[] sieve(int n) {
    boolean[] composite = new boolean[n + 1];
    composite[0] = composite[1] = true;

    for (int i = 2; (long) i * i <= n; i++) {
        if (!composite[i]) {
            for (int j = i * i; j <= n; j += i) {
                composite[j] = true;
            }
        }
    }
    return composite;                  // composite[k] == false means k is prime
}

Two details carry the efficiency. The outer loop stops at √n, because any composite has a factor at or below its square root. The inner loop starts at i * i rather than 2 * i, because smaller multiples of i were already crossed off by smaller primes.

Note the (long) i * i cast in the outer condition — for n near 2 × 109, i * i would overflow int.

A sieve up to 107 uses about 10 MB as a boolean[] and runs in well under a second. Up to 108 it becomes tight on both.

Smallest prime factor

A variant that stores, for each number, its smallest prime factor. This makes factorizing any number in the range O(log n) with no division loop.

static int[] smallestPrimeFactor(int n) {
    int[] spf = new int[n + 1];
    for (int i = 2; i <= n; i++) {
        if (spf[i] == 0) {                        // i is prime
            for (int j = i; j <= n; j += i) {
                if (spf[j] == 0) spf[j] = i;
            }
        }
    }
    return spf;
}

static List<Integer> factorize(int x, int[] spf) {
    List<Integer> factors = new ArrayList<>();
    while (x > 1) {
        int p = spf[x];
        while (x % p == 0) {
            factors.add(p);
            x /= p;
        }
    }
    return factors;
}

Use this when a problem asks you to factorize many numbers all below some bound. Building the table once beats factorizing each number separately.

Factorizing a single large number

Without a precomputed table, divide out each factor as you find it.

static List<Long> primeFactors(long n) {
    List<Long> factors = new ArrayList<>();
    for (long d = 2; d * d <= n; d++) {
        while (n % d == 0) {
            factors.add(d);
            n /= d;
        }
    }
    if (n > 1) factors.add(n);         // whatever remains is prime
    return factors;
}

The final if (n > 1) is essential and easy to forget. After dividing out everything up to √n, any remainder greater than 1 is itself a prime larger than √n — for example factorizing 14 leaves 7 after removing 2.

This is O(√n) and returns factors with multiplicity, so 12 gives [2, 2, 3].

GCD and LCM

The Euclidean algorithm, which is O(log min(a, b)).

static long gcd(long a, long b) {
    return b == 0 ? a : gcd(b, a % b);
}

static long lcm(long a, long b) {
    return a / gcd(a, b) * b;          // divide first to avoid overflow
}

In lcm, dividing before multiplying matters. Writing a * b / gcd(a, b) computes the product first, which can overflow even when the final result would fit.

Java also provides java.math.BigInteger.gcd, but the three-line version is faster and avoids allocation.

Costs at a glance

TaskApproachCostPractical limit
All divisors of nLoop to √nO(√n)n ≤ 1012
Is n prime?Trial divisionO(√n)n ≤ 1014
All primes up to NSieveO(N log log N)N ≤ 107
Factorize many small numbersSmallest-prime-factor tableO(N log N) build, O(log x) eachN ≤ 107
Factorize one large numberDivide out factorsO(√n)n ≤ 1012
GCDEuclidO(log n)any long

Common mistakes

  • Treating 1 as prime. It is not. Check n < 2 first.
  • Forgetting the leftover factor after the factorization loop.
  • d <= Math.sqrt(n) instead of d * d <= n, which can misbehave at the boundary.
  • Overflow in lcm. Divide before multiplying.
  • i * i overflowing int in a sieve for large N.
  • Sieving per query instead of once. Build the table before reading the queries.
  • Assuming a number has few divisors without checking.

Practice

Pick the approach from the cost table before writing code.

  1. Print all primes up to 106.
  2. Given n up to 1012, print its prime factorization.
  3. Given 105 numbers, each up to 106, print the number of distinct prime factors of each.
  4. Given n, print the number of divisors of n! — no, print the number of trailing zeros of n! in base 10, for n up to 1018.
  5. Given an array, print the GCD of all elements, then the LCM modulo 109+7.
Hints
  1. Sieve, then scan.
  2. Divide out factors to √n, remembering the leftover.
  3. Build a smallest-prime-factor table once to 106, then factorize each query in O(log x). Count distinct primes, not multiplicity.
  4. Trailing zeros come from factors of 10, so count factors of 5 — there are always more 2s. Sum n/5 + n/25 + n/125 + ..., stopping when the term reaches zero. Use long and divide rather than computing powers.
  5. GCD folds directly. LCM under a modulus is subtle: you cannot use the divide-by-GCD formula once values are reduced. Factorize each element and take the maximum exponent of each prime, then multiply those prime powers modulo the answer. See Modular Arithmetic.

Related