Many competitive programming problems require mathematical insights. Modular arithmetic, prime factorization, and combinatorics appear in 30%+ of CF problems rated 1400+.
When numbers get too large, we work modulo some prime (usually 10^9 + 7).Why modulo 10^9 + 7?
It’s a prime number (required for modular inverse via Fermat’s theorem)
It fits in a 32-bit integer
Two numbers modulo this can be multiplied without overflow in 64-bit
The Core Idea: Instead of computing huge numbers like 21000000, we keep everything “wrapped around” within [0,MOD−1]. Think of it like a clock—after 12, we go back to 1.Key Properties (these let us modulo at every step):
(a+b)modm=((amodm)+(bmodm))modm
(a×b)modm=((amodm)×(bmodm))modm
(a−b)modm=((amodm)−(bmodm)+m)modm (add m to handle negatives)
Copy
const ll MOD = 1e9 + 7;// Properties:// (a + b) % m = ((a % m) + (b % m)) % m// (a * b) % m = ((a % m) * (b % m)) % m// (a - b) % m = ((a % m) - (b % m) + m) % m // Add m to handle negativell add(ll a, ll b) { return ((a % MOD) + (b % MOD)) % MOD; }ll sub(ll a, ll b) { return ((a % MOD) - (b % MOD) + MOD) % MOD; }ll mul(ll a, ll b) { return ((a % MOD) * (b % MOD)) % MOD; }
Division is Different!(a / b) % m ≠ ((a % m) / (b % m)) % mFor division, multiply by modular inverse: a / b ≡ a × b^(-1) (mod m)
The Problem: Computing an directly takes O(n) multiplications. For n=1018, that’s impossible.The Insight: Use the property that:
If n is even: an=(an/2)2
If n is odd: an=a×an−1
Example: Computing 313:
313=3×312
312=(36)2
36=(33)2
33=3×32
32=(31)2
31=3×30
This reduces O(n) to O(log n) multiplications!Bit Interpretation: 13 in binary is 1101. We compute 31,32,34,38 and multiply those where the bit is 1: 313=38×34×31.
Copy
ll power(ll base, ll exp, ll mod = MOD) { ll result = 1; base %= mod; while (exp > 0) { if (exp & 1) result = result * base % mod; base = base * base % mod; exp >>= 1; } return result;}
What is it? The modular inverse of a modulo m is a number a−1 such that:
a×a−1≡1(modm)Think of it as “division in modular world.” To compute axmodm, we compute x×a−1modm.Fermat’s Little Theorem: For prime p and a not divisible by p:
ap−1≡1(modp)Rearranging: a×ap−2≡1(modp), so a−1≡ap−2(modp)When does inverse exist?
For prime modulus: Always exists if gcd(a,p)=1 (i.e., a≡0)
For general modulus: Only if gcd(a,m)=1
Copy
ll modinv(ll a, ll mod = MOD) { return power(a, mod - 2, mod);}ll divide(ll a, ll b, ll mod = MOD) { return mul(a, modinv(b, mod));}
The Problem: Find integers x,y such that ax+by=gcd(a,b)Why it matters: This is Bézout’s Identity—such x,y always exist!How it works:
Base case: When b=0, we have gcd(a,0)=a, so x=1,y=0 works: a(1)+0(0)=a
Recursive case: If we know x′,y′ for (b,amodb) such that bx′+(amodb)y′=g, then:
amodb=a−⌊a/b⌋⋅b
Substituting: bx′+(a−⌊a/b⌋⋅b)y′=g
Rearranging: ay′+b(x′−⌊a/b⌋⋅y′)=g
So x=y′ and y=x′−⌊a/b⌋⋅y′
Application: Finding modular inverse when modulus is not prime. If gcd(a,m)=1, then from ax+my=1, we get ax≡1(modm), so x is the inverse.
Copy
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 for any coprime a, m (not just prime m)ll modinv_general(ll a, ll m) { ll x, y; ll g = extgcd(a, m, x, y); if (g != 1) return -1; // No inverse exists return (x % m + m) % m;}
The Problem: Find all prime numbers up to N.The Insight: Instead of testing each number for primality (expensive), we “cross out” multiples of each prime.Algorithm:
Start with all numbers marked as potentially prime
The first unmarked number (2) is prime—cross out all its multiples (4, 6, 8, …)
Next unmarked (3) is prime—cross out 6, 9, 12, …
For number i, start crossing from i2 (smaller multiples already crossed by smaller primes)
Repeat until i2>N
Why O(N log log N)? Each prime p crosses out N/p numbers. The sum ∑p≤NN/p=N∑p≤N1/p is O(NloglogN) by the prime harmonic series.Visual Example for N = 20:
Copy
Start: 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20After 2: ✓ 3 X 5 X 7 X 9 X 11 X 13 X 15 X 17 X 19 XAfter 3: ✓ ✓ X 5 X 7 X X X 11 X 13 X X X 17 X 19 XAfter 5: (5² = 25 > 20, so we stop)Primes: 2, 3, 5, 7, 11, 13, 17, 19
Copy
const int MAXN = 1e7 + 5;bool isPrime[MAXN];vector<int> primes;void sieve() { fill(isPrime, isPrime + MAXN, true); isPrime[0] = isPrime[1] = false; for (int i = 2; i < MAXN; i++) { if (isPrime[i]) { primes.push_back(i); for (ll j = (ll)i * i; j < MAXN; j += i) { isPrime[j] = false; } } }}
The Improvement: Standard sieve can mark a composite multiple times (12 is marked by both 2 and 3). The linear sieve ensures each composite is marked exactly once—by its smallest prime factor (SPF).Key Invariant: When processing number i:
We multiply i by primes p≤spf[i]
This ensures i⋅p is marked by its SPF, which is p
We stop when p>spf[i] because then i⋅p would have SPF = spf[i], not p
Why SPF is useful:
O(log n) factorization: repeatedly divide by SPF
Euler’s totient: compute multiplicatively using prime factorization
Copy
int spf[MAXN]; // Smallest Prime Factorvoid linearSieve() { for (int i = 2; i < MAXN; i++) { if (spf[i] == 0) { spf[i] = i; primes.push_back(i); } for (int p : primes) { if (p > spf[i] || (ll)i * p >= MAXN) break; spf[i * p] = p; } }}// O(log n) factorization using SPFvector<pair<int, int>> factorize(int n) { vector<pair<int, int>> factors; while (n > 1) { int p = spf[n], cnt = 0; while (n % p == 0) { n /= p; cnt++; } factors.push_back({p, cnt}); } return factors;}
vector<pair<ll, int>> factorize(ll n) { vector<pair<ll, int>> factors; for (ll p = 2; p * p <= n; p++) { if (n % p == 0) { int cnt = 0; while (n % p == 0) { n /= p; cnt++; } factors.push_back({p, cnt}); } } if (n > 1) factors.push_back({n, 1}); return factors;}
ll countDivisors(ll n) { ll cnt = 1; for (ll p = 2; p * p <= n; p++) { if (n % p == 0) { int exp = 0; while (n % p == 0) { n /= p; exp++; } cnt *= (exp + 1); } } if (n > 1) cnt *= 2; return cnt;}// Get all divisorsvector<ll> getDivisors(ll n) { vector<ll> divs; for (ll i = 1; i * i <= n; i++) { if (n % i == 0) { divs.push_back(i); if (i != n / i) divs.push_back(n / i); } } sort(divs.begin(), divs.end()); return divs;}
The Problem: Computing (rn)=r!(n−r)!n! for many queries.Naive approach: Compute each query in O(n)—too slow for 105 queries.Smart approach: Precompute n! and (n!)−1 for all n.The Trick for Inverse Factorials: Instead of computing n modular inverses (each costs O(log MOD)):
Compute (MAXN−1)!−1 using Fermat’s theorem
Use the recurrence: (n!)−1=(n+1)!⋅(n+1)−1… but even simpler:
(n!)−1=((n+1)!)−1⋅(n+1)
This is because (n+1)!=n!⋅(n+1), so (n!)−1=((n+1)!)−1⋅(n+1)
This gives O(N) precomputation and O(1) per query.
Copy
const int MAXN = 2e5 + 5;ll fact[MAXN], inv_fact[MAXN];void precompute() { fact[0] = 1; for (int i = 1; i < MAXN; i++) { fact[i] = fact[i-1] * i % MOD; } // Only ONE modular inverse computation! inv_fact[MAXN-1] = power(fact[MAXN-1], MOD - 2); // The rest computed in O(1) each for (int i = MAXN - 2; i >= 0; i--) { inv_fact[i] = inv_fact[i+1] * (i+1) % MOD; }}ll C(int n, int r) { if (r < 0 || r > n) return 0; return fact[n] * inv_fact[r] % MOD * inv_fact[n-r] % MOD;}ll P(int n, int r) { if (r < 0 || r > n) return 0; return fact[n] * inv_fact[n-r] % MOD;}
The Problem: Computing (rn)modp when n is huge (like 1018) but p is a small prime.Why standard approach fails: We can’t precompute factorials up to 1018.Lucas Theorem:
(rn)≡∏i=0k(rini)(modp)where n=nkpk+...+n1p+n0 and r=rkpk+...+r1p+r0 are base-p representations.Intuition: Break down n and r into their “digits” in base p. Each digit contributes a small binomial coefficient (computable since values are < p).Example: (512)mod3
12 in base 3: 110 → digits are (1, 1, 0)
5 in base 3: 012 → digits are (0, 1, 2)
(512)≡(01)⋅(11)⋅(20)=1⋅1⋅0=0(mod3)
Copy
ll lucas(ll n, ll r, ll p) { if (r == 0) return 1; return C(n % p, r % p) * lucas(n / p, r / p, p) % p;}
Definition: ϕ(n) = count of integers in [1,n] that are coprime to n (i.e., gcd(k,n)=1).Examples:
ϕ(1)=1 (just 1 itself)
ϕ(6)=2 (1 and 5 are coprime to 6)
ϕ(7)=6 (for prime p: all of 1, 2, …, p-1 are coprime)
ϕ(p)=p−1 for any prime p
Formula: If n=p1a1×p2a2×...×pkak, then:
ϕ(n)=n×∏i=1k(1−pi1)=n×p1p1−1×p2p2−1×...×pkpk−1Intuition: Start with n numbers. For each prime factor p, exactly 1/p of numbers are divisible by p, so we keep (1−1/p) of them.Example: ϕ(12)=ϕ(22×3)=12×(1−1/2)×(1−1/3)=12×1/2×2/3=4Indeed, the numbers coprime to 12 in [1,12] are: 1, 5, 7, 11 → 4 numbers ✓
Copy
ll phi(ll n) { ll result = n; for (ll p = 2; p * p <= n; p++) { if (n % p == 0) { while (n % p == 0) n /= p; result -= result / p; } } if (n > 1) result -= result / n; return result;}// Sieve for phiint phi_sieve[MAXN];void computePhi() { iota(phi_sieve, phi_sieve + MAXN, 0); for (int i = 2; i < MAXN; i++) { if (phi_sieve[i] == i) { // i is prime for (int j = i; j < MAXN; j += i) { phi_sieve[j] -= phi_sieve[j] / i; } } }}
The Problem: Count elements satisfying at least one of k conditions.The Principle:
∣A1∪A2∪...∪Ak∣=∑∣Ai∣−∑∣Ai∩Aj∣+∑∣Ai∩Aj∩Ak∣−...Alternately: Add single sets, subtract pairs, add triples, subtract quadruples, …Why it works (for 2 sets):
∣A∪B∣ counts elements in either A or B
∣A∣+∣B∣ overcounts elements in both by 1
So ∣A∪B∣=∣A∣+∣B∣−∣A∩B∣
Bitmask Implementation: With k conditions, use a k-bit mask where bit i = 1 means “include condition i.” For each non-empty subset:
Count elements satisfying ALL conditions in the subset
Add if odd number of conditions, subtract if even
Classic Application: Count numbers in [1,n] divisible by at least one of the given primes.
Divisible by p: there are ⌊n/p⌋ such numbers
Divisible by both p and q: there are ⌊n/(p⋅q)⌋ such numbers
Copy
// Count numbers in [1, n] divisible by at least one prime in listll countDivisible(ll n, vector<ll>& primes) { int k = primes.size(); ll result = 0; for (int mask = 1; mask < (1 << k); mask++) { ll product = 1; int bits = 0; for (int i = 0; i < k; i++) { if (mask & (1 << i)) { product *= primes[i]; bits++; if (product > n) break; } } if (product <= n) { if (bits % 2 == 1) result += n / product; else result -= n / product; } } return result;}