Appearance
Number Theory
Essential toolkit for contest math: primes, divisors, modular arithmetic, and multiplicative functions. Appears constantly in Codeforces Div 2 B-D and is the backbone of any problem tagged math or number theory.
Difficulty: [Intermediate]Prerequisites: Math FundamentalsCF Rating Range: 1300-1800
What this file covers, in order:
- GCD and the Euclidean algorithm (revisited from a primes/divisibility angle).
- Sieve of Eratosthenes — primes up to
in . - Linear sieve and smallest-prime-factor table — primes plus
factorization. - Factorization in isolation (trial division up to
, Miller-Rabin / Pollard rho up to ). - Divisor counts and divisor sums.
- Euler's totient
and Mobius — multiplicative functions on the sieve. - Modular inverse via Fermat (revisited; full extgcd treatment lives in the next file).
Note on overlap with Math Fundamentals. Fundamentals teaches the GCD algorithm. This file teaches what GCD reveals about primes, divisibility, and factorization patterns — and how those interact with sieves and multiplicative functions. The repetition is deliberate, but it has a different teaching purpose.
Quick Revisit
- USE WHEN: problem involves primes, divisors, factorization, or Euler's totient
- INVARIANT: every integer > 1 has a unique prime factorization
- TIME: O(n log log n) sieve, O(sqrt(n)) factorization | SPACE: O(n) for sieve
- CLASSIC BUG: sieve inner loop starting at 2i instead of ii (correct but unnecessarily slow)
- PRACTICE: see practice problems in this chapter
Contents
- Intuition
- C++ STL Reference
- Implementation (Contest-Ready)
- Operations Reference
- Problem Patterns & Tricks
- Contest Cheat Sheet
- Gotchas & Debugging
- Self-Test
- Practice Problems
- Further Reading
Contest Frequency: ** Regular -- modular arithmetic appears frequently
Intuition
SIEVE OF ERATOSTHENES -- CROSSING OUT COMPOSITES:
Start: all marked prime
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
p=2: cross out 4,6,8,10,12,14,16,18,20 (start from 2^2=4)
2 3 X 5 X 7 X X X 11 X 13 X X X 17 X 19 X
p=3: cross out 9,15 (start from 3^2=9; 6,12,18 already crossed)
2 3 X 5 X 7 X X X 11 X 13 X X X 17 X 19 X
p=4: skip (composite). p=5: 5^2=25 > 20 -> STOP.
Primes <= 20: {2, 3, 5, 7, 11, 13, 17, 19}You need
d: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
48: Y Y Y Y N Y N Y N N N Y N N N Y N N
18: Y Y Y N N Y N N Y N N N N N N N N Y
* * * *That is
The Euclidean algorithm replaces the problem
Why does this work? If
Think of it like making change: instead of counting coins one by one, you hand over the largest bills first and only deal with the leftover. Each "mod" operation is a big leap, not a tiny step.
Where it breaks: the inputs must be non-negative integers. Passing negative values to % in C++ gives implementation-defined signs before C++11 and truncation-toward-zero since C++11 -- always ensure
Visual Walkthrough
Compute
Step a b a mod b Reasoning
---- ---- ---- -------- -----------------------------------
1 48 18 12 48 = 2*18 + 12 --> gcd(48,18) = gcd(18,12)
2 18 12 6 18 = 1*12 + 6 --> gcd(18,12) = gcd(12, 6)
3 12 6 0 12 = 2* 6 + 0 --> gcd(12, 6) = gcd( 6, 0)
4 6 0 --- b = 0, done --> answer = 64 steps, 4 operations. The naive scan checked 18 candidates. For larger inputs the gap explodes:
Worked Example: Sieve of Eratosthenes for N = 30
Initial array (all marked prime):
2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
── p = 2: cross out 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 ──
2 3 x 5 x 7 x 9 x
11 x 13 x 15 x 17 x 19 x
21 x 23 x 25 x 27 x 29 x
(14 composites marked)
── p = 3: cross out 9, 15, 21, 27 (start at 3² = 9) ──
2 3 x 5 x 7 x x x
11 x 13 x x x 17 x 19 x
x x 23 x 25 x x x 29 x
(4 new composites)
── p = 5: cross out 25 (start at 5² = 25) ──
2 3 x 5 x 7 x x x
11 x 13 x x x 17 x 19 x
x x 23 x x x x x 29 x
(1 new composite)
Next prime: p = 7. But 7² = 49 > 30 -> STOP.
Primes <= 30: { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 } (10 primes)
Total inner-loop iterations: 14 + 4 + 1 = 19
(matches the ∑ N/p ~= N*ln(ln N) bound)In C++20 the standard library gives you this for free:
cpp
#include <numeric>
int g = std::gcd(48, 18); // 6The Invariant
+------------------------------------------------------------------+
| INVARIANT |
| |
| gcd(a, b) = gcd(b, a mod b) for all a >= 0, b > 0 |
| |
| CONVERGENCE GUARANTEE |
| The remainder (a mod b) < b, and after every two consecutive |
| steps the larger argument drops by at least half. |
| Therefore the algorithm terminates in at most |
| 2 * floor(log2(min(a, b))) + 2 steps = O(log min(a, b)). |
+------------------------------------------------------------------+Trace it in the walkthrough:
When to Reach for This
Trigger phrases in problem statements:
| Phrase | Why it signals number theory |
|---|---|
| "gcd", "greatest common divisor" | Direct application of Euclidean algorithm |
| "lcm", "least common multiple" | |
| "coprime", " | Coprimality checks, Euler's totient, Mobius function |
| "Euler's totient", " | Counts integers coprime to |
| "modular inverse", " | Requires |
| "answer modulo | Modular arithmetic pipeline: binpow + modinv |
| "number of divisors", " | Prime factorization, then |
Counter-examples -- when this is NOT the right tool:
- "Find the
-th prime" -- this is a sieve problem, not a GCD problem. - "Maximize GCD by choosing a subset" -- often greedy or DP on divisors, not raw Euclidean algorithm.
- "Count multiples in a range" -- arithmetic, not number theory machinery. Just
.
What the Code Won't Teach You
The sieve code runs; modinv returns the right number. What remains invisible:
The scale guide is the real skill. Knowing which algorithm to reach for at each input size is more important than knowing the algorithms themselves. Trial division up to
FACTORIZATION / PRIMALITY SCALE GUIDE:
+----------------------+------------------------+--------------+
| n range | Primality test | Factorize |
+----------------------+------------------------+--------------+
| n <= 10^7 | Precomputed sieve O(1) | SPF sieve |
| n <= 10^12 | Trial div O(sqrt n) | Trial div |
| n <= 10^18 | Miller-Rabin O(log^2 n)| Pollard rho |
+----------------------+------------------------+--------------+
Wrong tool at wrong scale = TLE or MLE.Multiplicative functions compose with the sieve, not with each other.phi[p*n] = phi[n] * (p-1) tells you why the branch exists.
C++ STL Reference
| Function / Class | Header | What it does | Time Complexity |
|---|---|---|---|
__gcd(a, b) / gcd(a, b) | <algorithm> / <numeric> | Greatest common divisor. gcd (C++17) handles 0 correctly. | |
lcm(a, b) | <numeric> | Least common multiple (C++17). Watch overflow: lcm may exceed long long. | |
__int128 | built-in | 128-bit integer. Use when intermediate products overflow long long. | -- |
bitset<N> | <bitset> | Compact boolean array. Can be used as a sieve with | |
vector<bool> | <vector> | Bit-packed boolean vector. Fine for sieve, but [] returns proxy. | |
pow / powl | <cmath> | Avoid for integer work -- floating point errors. Write your own binpow. | -- |
Implementation (Contest-Ready)
Version 1: Minimal Contest Template
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MOD = 1e9 + 7;
// --- Sieve of Eratosthenes ---
vector<int> sieve(int n) {
vector<bool> is_prime(n + 1, true);
is_prime[0] = is_prime[1] = false;
for (int i = 2; i * i <= n; i++)
if (is_prime[i])
for (int j = i * i; j <= n; j += i)
is_prime[j] = false;
vector<int> primes;
for (int i = 2; i <= n; i++)
if (is_prime[i]) primes.push_back(i);
return primes;
}
// --- Smallest Prime Factor sieve ---
vector<int> spf_sieve(int n) {
vector<int> spf(n + 1);
iota(spf.begin(), spf.end(), 0);
for (int i = 2; i * i <= n; i++)
if (spf[i] == i)
for (int j = i * i; j <= n; j += i)
if (spf[j] == j) spf[j] = i;
return spf;
}
// --- Factorize using SPF table ---
vector<pair<int,int>> factorize(int n, const vector<int>& spf) {
vector<pair<int,int>> factors;
while (n > 1) {
int p = spf[n], cnt = 0;
while (n % p == 0) { n /= p; cnt++; }
factors.emplace_back(p, cnt);
}
return factors;
}
// --- Trial division factorization (no sieve needed) ---
vector<pair<ll,int>> factorize(ll n) {
vector<pair<ll,int>> factors;
for (ll d = 2; d * d <= n; d++) {
if (n % d == 0) {
int cnt = 0;
while (n % d == 0) { n /= d; cnt++; }
factors.emplace_back(d, cnt);
}
}
if (n > 1) factors.emplace_back(n, 1);
return factors;
}
// --- Binary exponentiation ---
ll binpow(ll base, ll exp, ll mod) {
ll result = 1;
base %= mod;
while (exp > 0) {
if (exp & 1) result = result * base % mod;
base = base * base % mod;
exp >>= 1;
}
return result;
}
// --- Modular inverse (mod must be prime) ---
ll modinv(ll a, ll mod) {
return binpow(a, mod - 2, mod);
}
// --- Extended GCD ---
ll extgcd(ll a, ll b, ll &x, ll &y) {
if (b == 0) { x = 1; y = 0; return a; }
ll x1, y1;
ll g = extgcd(b, a % b, x1, y1);
x = y1;
y = x1 - (a / b) * y1;
return g;
}
// --- Modular inverse (general, mod need not be prime) ---
ll modinv_general(ll a, ll mod) {
ll x, y;
ll g = extgcd(a, mod, x, y);
if (g != 1) return -1; // inverse doesn't exist
return (x % mod + mod) % mod;
}
// --- Euler's totient for single n ---
ll euler_phi(ll n) {
ll result = n;
for (ll d = 2; d * d <= n; d++) {
if (n % d == 0) {
while (n % d == 0) n /= d;
result -= result / d;
}
}
if (n > 1) result -= result / n;
return result;
}
// --- Euler's totient sieve ---
vector<int> phi_sieve(int n) {
vector<int> phi(n + 1);
iota(phi.begin(), phi.end(), 0);
for (int i = 2; i <= n; i++) {
if (phi[i] == i) { // i is prime
for (int j = i; j <= n; j += i)
phi[j] -= phi[j] / i;
}
}
return phi;
}
int main() {
// Example: factorize and compute inverse
auto primes = sieve(1000000);
auto sp = spf_sieve(1000000);
int n = 360;
auto f = factorize(n, sp);
for (auto [p, e] : f) cout << p << "^" << e << " ";
cout << "\n";
ll inv5 = modinv(5, MOD);
cout << "5^{-1} mod 1e9+7 = " << inv5 << "\n";
cout << "check: " << 5LL * inv5 % MOD << "\n";
}Version 2: Explained Version
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MOD = 1e9 + 7;
// ======= Sieve of Eratosthenes =======
// Returns a list of all primes <= n.
// Time: O(n log log n). Space: O(n).
// We only start crossing out at i*i because smaller multiples
// of i were already marked by smaller primes.
vector<int> sieve(int n) {
vector<bool> is_prime(n + 1, true);
is_prime[0] = is_prime[1] = false;
for (int i = 2; i * i <= n; i++) {
if (is_prime[i]) {
// Mark all multiples of i starting from i^2.
// Multiples below i^2 (like 2*i, 3*i, ...) were already
// marked when we processed 2, 3, etc.
for (int j = i * i; j <= n; j += i)
is_prime[j] = false;
}
}
vector<int> primes;
for (int i = 2; i <= n; i++)
if (is_prime[i]) primes.push_back(i);
return primes;
}
// ======= Smallest Prime Factor (SPF) Sieve =======
// spf[i] = smallest prime that divides i.
// Allows O(log n) factorization of any n in range.
// iota fills spf[i] = i, so primes keep spf[p] = p.
vector<int> spf_sieve(int n) {
vector<int> spf(n + 1);
iota(spf.begin(), spf.end(), 0); // spf[i] = i initially
for (int i = 2; i * i <= n; i++) {
if (spf[i] == i) { // i is prime (nobody set a smaller factor)
for (int j = i * i; j <= n; j += i) {
if (spf[j] == j) // only update if not already set
spf[j] = i;
}
}
}
return spf;
}
// Factorize n using SPF table: repeatedly divide by spf[n].
// Returns vector of (prime, exponent) pairs.
vector<pair<int,int>> factorize(int n, const vector<int>& spf) {
vector<pair<int,int>> factors;
while (n > 1) {
int p = spf[n], cnt = 0;
while (n % p == 0) { n /= p; cnt++; }
factors.emplace_back(p, cnt);
}
return factors;
}
// ======= Binary Exponentiation =======
// Computes base^exp % mod in O(log exp).
// Key idea: express exp in binary. For each bit, either
// multiply result by base (if bit is 1) or skip.
// Always keep intermediate values < mod^2 to avoid overflow.
// With mod ~ 1e9, base*base ~ 1e18 which fits in long long.
ll binpow(ll base, ll exp, ll mod) {
ll result = 1;
base %= mod; // ensure base < mod to start
while (exp > 0) {
if (exp & 1) // current bit is 1
result = result * base % mod;
base = base * base % mod; // square the base
exp >>= 1; // shift to next bit
}
return result;
}
// ======= Modular Inverse via Fermat's Little Theorem =======
// If mod is prime, a^{mod-1} = 1 (mod), so a^{-1} = a^{mod-2}.
// This is the go-to method for MOD = 1e9+7.
ll modinv(ll a, ll mod) {
return binpow(a, mod - 2, mod);
}
// ======= Extended GCD =======
// Finds x, y such that a*x + b*y = gcd(a, b).
// When gcd(a, mod) = 1, the x gives us a^{-1} mod b.
// Works for any modulus, not just prime.
ll extgcd(ll a, ll b, ll &x, ll &y) {
if (b == 0) {
x = 1; y = 0;
return a;
}
ll x1, y1;
ll g = extgcd(b, a % b, x1, y1);
// Back-substitute: a*x + b*y = g
// becomes b*x1 + (a%b)*y1 = g
// => a*y1 + b*(x1 - (a/b)*y1) = g
x = y1;
y = x1 - (a / b) * y1;
return g;
}
// Modular inverse via ExtGCD. Returns -1 if no inverse.
ll modinv_general(ll a, ll mod) {
ll x, y;
ll g = extgcd(a, mod, x, y);
if (g != 1) return -1;
return (x % mod + mod) % mod; // ensure positive
}
// ======= Euler's Totient =======
// phi(n) = count of k in [1,n] with gcd(k,n) = 1.
// Formula: phi(n) = n * product of (1 - 1/p) for each prime p | n.
// We compute it during trial division to avoid floating point.
ll euler_phi(ll n) {
ll result = n;
for (ll d = 2; d * d <= n; d++) {
if (n % d == 0) {
while (n % d == 0) n /= d;
result -= result / d; // equivalent to result *= (1 - 1/d)
}
}
if (n > 1) result -= result / n; // remaining prime factor
return result;
}
// ======= Totient Sieve =======
// Computes phi[i] for all i in [0, n].
// Same idea as prime sieve: if i is prime, update all multiples.
vector<int> phi_sieve(int n) {
vector<int> phi(n + 1);
iota(phi.begin(), phi.end(), 0); // phi[i] = i initially
for (int i = 2; i <= n; i++) {
if (phi[i] == i) { // i is prime
for (int j = i; j <= n; j += i)
phi[j] -= phi[j] / i; // multiply by (1 - 1/i)
}
}
return phi;
}
int main() {
auto sp = spf_sieve(1000000);
// Factorize 360 = 2^3 * 3^2 * 5
auto f = factorize(360, sp);
for (auto [p, e] : f)
cout << p << "^" << e << " ";
cout << "\n"; // Output: 2^3 3^2 5^1
// Modular inverse: 5 * inv(5) = 1 (mod 1e9+7)
ll inv5 = modinv(5, MOD);
cout << 5LL * inv5 % MOD << "\n"; // Output: 1
// Euler's totient: phi(360) = 96
cout << euler_phi(360) << "\n"; // Output: 96
}Operations Reference
| Operation | Time | Space |
|---|---|---|
| Sieve of Eratosthenes (primes up to | ||
| SPF sieve (smallest prime factor up to | ||
| Factorize via SPF table | ||
| Trial division factorization | ||
| Binary exponentiation | ||
| Modular inverse (Fermat) | ||
| Extended GCD | ||
| Euler's totient (single | ||
| Euler's totient sieve up to | ||
| GCD (Euclidean) |
🔍 Why This Works -- Euler's Totient is Multiplicative
Claim: If
Proof sketch via CRT: By the Chinese Remainder Theorem, when
So the count of integers coprime to
This is why
Problem Patterns & Tricks
Pattern 1: Sieve + Query
Precompute a sieve (prime, SPF, or totient) up to
cpp
auto sp = spf_sieve(1000000);
while (q--) {
int n; cin >> n;
auto f = factorize(n, sp);
// answer using f
}Problems: CF 576A, CF 154B
Pattern 2: Divisor Enumeration
Enumerate all divisors of
cpp
// All divisors of every number up to N
vector<vector<int>> divisors(N + 1);
for (int d = 1; d <= N; d++)
for (int m = d; m <= N; m += d)
divisors[m].push_back(d);Problems: CF 1512G, CF 236B
Pattern 3: "Modulo 1e9+7" -- Modular Arithmetic Pipeline
When the answer involves factorials, binomial coefficients, or counting: precompute factorial and inverse factorial arrays, then answer combinations in
cpp
vector<ll> fac(N+1), inv_fac(N+1);
fac[0] = 1;
for (int i = 1; i <= N; i++) fac[i] = fac[i-1] * i % MOD;
inv_fac[N] = modinv(fac[N], MOD);
for (int i = N - 1; i >= 0; i--) inv_fac[i] = inv_fac[i+1] * (i+1) % MOD;
auto C = [&](int n, int k) -> ll {
if (k < 0 || k > n) return 0;
return fac[n] % MOD * inv_fac[k] % MOD * inv_fac[n-k] % MOD;
};Problems: CF 1462E, CF 1312D
Pattern 4: GCD/LCM Manipulation
Problems asking about GCD of subarrays, LCM of subsets, or "make all elements equal by GCD operations." Key insight: GCD of a range can only decrease (or stay) as you extend the range, giving at most
Problems: CF 1632D, CF 475D
Pattern 5: Euler's Totient in Counting
EULER'S TOTIENT phi(12) -- WHICH NUMBERS ARE COPRIME TO 12?
12 = 2^2 * 3
1 2 3 4 5 6 7 8 9 10 11 12
Y . . . Y . Y . . . Y .
Y = gcd(k, 12) = 1 -> {1, 5, 7, 11} -> phi(12) = 4
Formula: 12 * (1 - 1/2) * (1 - 1/3) = 12 * 1/2 * 2/3 = 4"Count pairs
Problems: CF 1295D, CF 431C
Pattern 6: Multiplicative Functions + Sieve
Problems where you need to compute a multiplicative function
Problems: CF 1139D, CF 757E
Pattern 7: Chinese Remainder Theorem (CRT)
Given multiple modular constraints, combine them into one. Used when a problem gives conditions modulo different primes or when you need to recover a value from remainders.
cpp
// CRT for two equations: x = r1 (mod m1), x = r2 (mod m2)
// Requires gcd(m1, m2) = 1
pair<ll,ll> crt(ll r1, ll m1, ll r2, ll m2) {
ll x, y;
extgcd(m1, m2, x, y);
ll mod = m1 * m2;
ll ans = (r1 + m1 * ((r2 - r1) % m2 * x % m2) % mod + mod) % mod;
return {ans, mod};
}Problems: CF 687B, CF 338D
Contest Cheat Sheet
+--------------------------------------------------------------+
| NUMBER THEORY CHEAT SHEET |
+--------------------------------------------------------------+
| WHEN TO USE: |
| - "count/check primes" --> sieve |
| - "answer modulo 1e9+7" --> binpow + modinv |
| - "gcd of range/subset" --> gcd properties |
| - "coprime pairs" --> Euler totient |
| - "multiple mod constraints" --> CRT |
+--------------------------------------------------------------+
| KEY CODE: |
| ll binpow(ll b, ll e, ll m) { |
| ll r=1; b%=m; |
| while(e>0){if(e&1)r=r*b%m; b=b*b%m; e>>=1;} return r; } |
| ll modinv(ll a, ll m) { return binpow(a, m-2, m); } |
+--------------------------------------------------------------+
| COMPLEXITIES: |
| Sieve to N : O(N log log N) time, O(N) space |
| SPF factorize : O(log n) per query after O(N) sieve |
| Trial division : O(sqrt(n)) per number |
| binpow / modinv : O(log n) time, O(1) space |
| ExtGCD : O(log n) time |
+--------------------------------------------------------------+
| OVERFLOW GUARD: 1LL * a * b % MOD (not a * b % MOD) |
| PRIME CHECK: MOD = 1e9+7 is prime --> use Fermat inverse |
+--------------------------------------------------------------+Gotchas & Debugging
Overflow on multiplication. Two
intvalues multiplied can overflowintsilently. Always cast:1LL * a * b % MOD. IfMODis near, even long long * long longoverflows -- use__int128or binary multiplication.Sieve size off-by-one. If you need primes up to
, allocate vector<bool>(N + 1). Accessingis_prime[N]on a size-vector is UB. Forgetting base case in binpow.
binpow(0, 0, MOD)returnsin the template above. Make sure that's what you want. Negative mod results. In C++,
-7 % 3 == -1, not2. Always do(x % MOD + MOD) % MODwhenxmight be negative.ExtGCD returning negative x. The
xfromextgcdcan be negative. Normalize:(x % mod + mod) % mod.Using
pow()from<cmath>. Floating-pointpow(2, 30)might return1073741823.9999and truncate to the wrong integer. Usebinpowor1LL << 30.Sieving too large. Sieve of
is fine (~10 MB). Sieve of needs ~100 MB and may TLE or MLE on CF. For up to , use trial division up to instead. Totient sieve vs single totient. Totient sieve up to
is . For a single large , use the trial-division formula. Inverse of 0.
modinv(0, MOD)is undefined. If your code computesbinpow(0, MOD-2, MOD), it returns0-- silently wrong. Guard against it.
Mental Traps
"Primality testing = sieve." Sieve is for bulk computation: all primes up to
PRIMALITY TESTING != PRIME ENUMERATION:
+----------------------------------------------------------+
| "Is 10^18 + 7 prime?" -> Miller-Rabin (per-query) |
| "List all primes <= 10^7" -> Sieve (bulk) |
| |
| WRONG: sieve up to 10^18 (impossible: 10^18 bytes)|
| WRONG: Miller-Rabin * 10^7 (works but 1000x slower) |
+----------------------------------------------------------+"Euler's theorem always works:
Common Mistakes
- Sieve inner loop starting at
2*iinstead ofi*i. Both are correct, but2*iis O(n log n) instead of O(n log log n). For n = 10^7 this matters. - Forgetting that 1 is not prime. Special-case n = 1 in primality tests and factorization routines.
- Euler's totient for prime powers.
phi(p^k) = p^{k-1} * (p - 1), notp^k - 1. The formula involves the prime base, not just the power. - Integer overflow in sieve.
i * ioverflowsintwheni > 46340. Use(long long)i * ior start the inner loop ati * iwith a long long variable. - Forgetting to handle the remaining factor. After trial division up to sqrt(n), if n > 1 then n itself is a prime factor. Omitting this loses the largest prime.
- Brute-forcing pairs when counting by GCD. For "count pairs
with " on , an loop TLEs, and "for each value, count coprime elements" is still . The right move is Möbius inversion: let cnt[d]= number of elements divisible byd, then recover pairs withgcd = dby inclusion-exclusion over multiples -- total. When you need to count pairs by GCD, think multiplicative functions and inversion, not brute force. The harmonic sum is , and it is your friend.
Debug Drills
Drill 1. Sieve marks primes as composite.
cpp
vector<bool> is_prime(n + 1, true);
is_prime[0] = is_prime[1] = false;
for (int i = 2; i <= n; i++) {
if (is_prime[i]) {
for (int j = i + i; j <= n; j += i)
is_prime[j] = false;
}
}
// Works, but TLE for n = 10^7. What's the optimization?What's wrong?
Start the inner loop at j = i * i instead of j = i + i. All multiples i*k where k < i have already been sieved by smaller primes. Also change outer loop to i * i <= n. This reduces work from O(N log log N) with a larger constant to the theoretical minimum.
Note: when i * i overflows int, use (long long)i * i <= n.
Drill 2. Wrong totient computation.
cpp
int phi(int n) {
int result = n;
for (int p = 2; p * p <= n; p++) {
if (n % p == 0) {
result -= result / p;
while (n % p == 0) n /= p;
}
}
// Missing something here?
return result;
}What's wrong?
If n > 1 after the loop, there's a remaining prime factor larger than √(original n). Add:
cpp
if (n > 1) result -= result / n;Without this, numbers like φ(14) = φ(2x7) would miss the factor of 7.
Drill 3. Integer overflow in factorization check.
cpp
vector<int> factorize(int n) {
vector<int> factors;
for (int d = 2; d * d <= n; d++) { // What if n ~ 10^9?
while (n % d == 0) {
factors.push_back(d);
n /= d;
}
}
if (n > 1) factors.push_back(n);
return factors;
}What's wrong?
d * d overflows int when d ~= 46341 (since 46341² > 2^31 - 1). Fix: use (long long)d * d <= n in the loop condition. Alternatively, precompute sqrt_n and compare d <= sqrt_n.
Self-Test
- [ ] Write the Sieve of Eratosthenes from memory -- including the
optimization and correct array bounds - [ ] Compute
by hand using and the formula - [ ] State Fermat's little theorem and use it to compute
without a calculator - [ ] Explain when trial division is fast enough vs when Miller-Rabin is needed -- give the size threshold
- [ ] State what "multiplicative function" means and give two examples (
, )
Practice Problems
| Rating | You should be able to... |
|---|---|
| CF 1200 | Implement basic sieve, check primality, compute GCD |
| CF 1500 | Use SPF sieve for O(log n) factorization; enumerate divisors in O(√n) |
| CF 1800 | Apply Euler's totient formula; use harmonic sieve for bulk divisor queries |
| CF 2100 | Implement Möbius inversion; solve multiplicative-function problems; optimize with Dirichlet convolution |
| # | Problem | Source | Difficulty | Key Idea |
|---|---|---|---|---|
| 1 | Almost Prime | CF 26A | 800 | Count numbers with exactly 2 distinct prime factors (sieve + factorize) |
| 2 | T-primes | CF 230B | 1300 | A number has exactly 3 divisors iff it's a perfect square of a prime |
| 3 | Noldbach Problem | CF 17A | 1000 | Sieve + check if prime is sum of two consecutive primes |
| 4 | Sherlock and his Girlfriend | CF 776B | 1200 | SPF sieve to separate primes from composites |
| 5 | Divisor Summation | CF 1512G | 1500 | Enumerate divisors via harmonic sieve, answer queries |
| 6 | Obtain the String | CF 1295D | 1600 | Euler totient application (Obtain a string via coprime counting) |
| 7 | Array GCD | CF 475D | 1700 | GCD of subarrays -- distinct GCD values are |
| 8 | Same GCDs | CF 1295D | 1700 | Direct Euler totient: count |
| 9 | Orac and LCM | CF 1350B | 1400 | Suffix GCD + pairwise LCM insight |
| 10 | Bash and a Tough Math Puzzle | CF 914D | 1900 | Segment tree on GCD with single-element replacement queries |
| 11 | Exponentiation | CSES | 1300 | Modular exponentiation |
| 12 | Counting Divisors | CSES | 1400 | Sieve-based divisor count for multiple queries |
| 13 | Common Divisors | CSES | 1500 | Find maximum GCD of any pair |
| 14 | Sum of Divisors | CSES | 1600 | Harmonic sum trick for divisor sums |
| 15 | Prime Multiples | CSES | 1700 | Inclusion-exclusion with prime factors |
Further Reading
- cp-algorithms: Sieve of Eratosthenes
- cp-algorithms: Prime Factorization
- cp-algorithms: Euler's Totient Function
- cp-algorithms: Modular Inverse
- cp-algorithms: Chinese Remainder Theorem
- cp-algorithms: Binary Exponentiation
- Codeforces Blog: Number Theory for CP -- Good overview of contest-relevant number theory.
Recap
- Sieve of Eratosthenes: mark composites in O(n log log n). Linear sieve gives O(n) with smallest prime factor for each number.
- Trial division: factor n in O(sqrt(n)) by testing primes up to sqrt(n). Don't forget the remaining factor if n > 1.
- Euler's totient:
phi(n) = n * product(1 - 1/p)over distinct prime factors p. Multiplicative function. - Key pattern: "factor the number, formula the answer" -- most number theory problems reduce to prime factorization plus a formula.
Cross-Links
- Math Fundamentals -- modular arithmetic foundations
- Extended Euclidean & CRT -- modular inverse and combining congruences
- Combinatorics Basics -- number theory feeds into counting problems