Master the essential competitive programming techniques — two pointers, sliding window, binary search on answer, prefix sums, sparse table, coordinate compression, BFS on implicit graphs, bitmask DP, and modular arithmetic — with implementation templates and when to apply each technique.
Introduction
These are the essential building blocks that appear constantly in competitive programming. Mastering these techniques is what separates participants who solve 2 problems per contest from those who solve 4-5.
2. Sliding Window
When to use: Fixed or variable-size subarray/substring problems.
# Maximum sum subarray of size k — O(n)
def max_sum_k(arr, k):
window = sum(arr[:k])
max_sum = window
for i in range(k, len(arr)):
window += arr[i] - arr[i-k]
max_sum = max(max_sum, window)
return max_sum
# Longest substring with at most k distinct characters — O(n)
from collections import defaultdict
def longest_k_distinct(s, k):
freq = defaultdict(int)
left = max_len = 0
for right, char in enumerate(s):
freq[char] += 1
while len(freq) > k:
freq[s[left]] -= 1
if freq[s[left]] == 0: del freq[s[left]]
left += 1
max_len = max(max_len, right - left + 1)
return max_len
3. Binary Search on Answer
When to use: "Find minimum/maximum X such that condition is feasible."
# Template
def binary_search_answer(lo, hi, feasible):
while lo < hi:
mid = (lo + hi) // 2
if feasible(mid):
hi = mid # Try to do better (minimize)
else:
lo = mid + 1 # This doesn't work, try larger
return lo
# Example: Koko eating bananas — minimum speed to eat all in h hours
def min_eating_speed(piles, h):
def can_eat(speed):
return sum((p + speed - 1) // speed for p in piles) <= h
return binary_search_answer(1, max(piles), can_eat)
# Example: Minimum days to make m bouquets
def min_days(bloom_day, m, k):
if m * k > len(bloom_day): return -1
def can_make(days):
bouquets = consecutive = 0
for b in bloom_day:
if b <= days: consecutive += 1; bouquets += consecutive == k; consecutive *= consecutive < k
else: consecutive = 0
return bouquets >= m
return binary_search_answer(min(bloom_day), max(bloom_day), can_make)
Recognize by: "Find minimum speed/capacity/size to achieve X," monotone feasibility function
4. Prefix Sums
When to use: Range sum queries in O(1) after O(n) preprocessing.
# 1D prefix sum
def build_prefix(arr):
prefix = [0] * (len(arr) + 1)
for i, x in enumerate(arr):
prefix[i+1] = prefix[i] + x
return prefix
def range_sum(prefix, l, r): # sum of arr[l..r] inclusive
return prefix[r+1] - prefix[l]
# 2D prefix sum — O(1) rectangle sum queries
def build_2d_prefix(matrix):
m, n = len(matrix), len(matrix[0])
prefix = [[0]*(n+1) for _ in range(m+1)]
for i in range(m):
for j in range(n):
prefix[i+1][j+1] = matrix[i][j] + prefix[i][j+1] + prefix[i+1][j] - prefix[i][j]
return prefix
def rect_sum(prefix, r1, c1, r2, c2): # sum of rectangle (r1,c1) to (r2,c2)
return prefix[r2+1][c2+1] - prefix[r1][c2+1] - prefix[r2+1][c1] + prefix[r1][c1]
# Prefix XOR — for range XOR queries
def build_xor_prefix(arr):
prefix = [0] * (len(arr) + 1)
for i, x in enumerate(arr): prefix[i+1] = prefix[i] ^ x
return prefix
def range_xor(prefix, l, r):
return prefix[r+1] ^ prefix[l]
5. Difference Array (Range Update)
When to use: Add a value to all elements in range [l, r] repeatedly, then query.
def range_add_queries(n, queries):
"""
queries = [(l, r, val), ...]
Add val to all elements arr[l..r]
Returns final array after all queries.
Time: O(n + q) vs O(n*q) naive
"""
diff = [0] * (n + 1)
for l, r, val in queries:
diff[l] += val
diff[r+1] -= val # Undo at r+1
# Reconstruct using prefix sum
result = [0] * n
current = 0
for i in range(n):
current += diff[i]
result[i] = current
return result
# Example: 1,000,000 range-add queries on array of size 1,000,000
# Naive: O(n*q) = O(10^12) — impossible
# Difference array: O(n+q) = O(2*10^6) — trivial
6. Sparse Table (Range Minimum/Maximum Query)
When to use: Static array, many range min/max queries in O(1).
import math
def build_sparse_table(arr):
"""
Build sparse table for range minimum queries.
Preprocessing: O(n log n), Query: O(1)
"""
n = len(arr)
LOG = max(1, int(math.log2(n)) + 1)
st = [[0]*n for _ in range(LOG)]
st[0] = arr[:]
for j in range(1, LOG):
for i in range(n - (1 << j) + 1):
st[j][i] = min(st[j-1][i], st[j-1][i + (1 << (j-1))])
log2 = [0] * (n + 1)
for i in range(2, n+1):
log2[i] = log2[i//2] + 1
return st, log2
def query_min(st, log2, l, r):
"""O(1) range minimum query."""
k = log2[r - l + 1]
return min(st[k][l], st[k][r - (1 << k) + 1])
7. Coordinate Compression
When to use: Values up to 10^9 but only ~10^5 distinct values needed.
def compress_coordinates(values):
"""
Map large values to small indices.
E.g., [100, 500, 200, 500, 100] → [0, 2, 1, 2, 0]
"""
sorted_unique = sorted(set(values))
rank = {v: i for i, v in enumerate(sorted_unique)}
return [rank[v] for v in values], sorted_unique
# Example: Segment tree on values up to 10^9
vals = [1000000000, 500000000, 1, 999999999, 500000000]
compressed, mapping = compress_coordinates(vals)
# Now use compressed (max value ~5) instead of vals (max value 10^9)
# Build segment tree on size len(mapping) instead of 10^9
8. Modular Arithmetic
When to use: Problems where the answer can be astronomically large; "output modulo 10^9+7"
MOD = 10**9 + 7
# Basic operations
def add(a, b): return (a + b) % MOD
def sub(a, b): return (a - b + MOD) % MOD
def mul(a, b): return (a * b) % MOD
def power(base, exp, mod=MOD): return pow(base, exp, mod) # Python built-in is fast
# Modular inverse (for division) — using Fermat's little theorem
# Only works when MOD is prime!
def mod_inverse(a, mod=MOD): return pow(a, mod - 2, mod)
def divide(a, b): return mul(a, mod_inverse(b))
# Precompute factorials and inverse factorials for combinations
def precompute_factorials(n, mod=MOD):
fact = [1] * (n + 1)
for i in range(1, n+1): fact[i] = fact[i-1] * i % mod
inv_fact = [1] * (n + 1)
inv_fact[n] = pow(fact[n], mod-2, mod)
for i in range(n-1, -1, -1): inv_fact[i] = inv_fact[i+1] * (i+1) % mod
return fact, inv_fact
def nCr(n, r, fact, inv_fact, mod=MOD):
if r < 0 or r > n: return 0
return fact[n] * inv_fact[r] % mod * inv_fact[n-r] % mod
9. Bitmask Dynamic Programming
When to use: State involves subsets of n ≤ 20 elements.
def traveling_salesman(dist, n):
"""
TSP: Find minimum cost Hamiltonian path visiting all cities.
State: (current_city, set_of_visited_cities)
Time: O(2^n × n²)
"""
INF = float('inf')
# dp[mask][i] = min cost to visit cities in mask, ending at city i
dp = [[INF] * n for _ in range(1 << n)]
dp[1][0] = 0 # Start at city 0, only city 0 visited
for mask in range(1 << n):
for u in range(n):
if dp[mask][u] == INF: continue
if not (mask >> u & 1): continue # u not in mask
for v in range(n):
if mask >> v & 1: continue # v already visited
new_mask = mask | (1 << v)
dp[new_mask][v] = min(dp[new_mask][v], dp[mask][u] + dist[u][v])
full = (1 << n) - 1
return min(dp[full][i] + dist[i][0] for i in range(n))
# Bitmask DP: Assignment problem
def min_cost_assignment(cost, n):
"""Assign n tasks to n workers, each exactly once, minimize total cost."""
dp = [float('inf')] * (1 << n)
dp[0] = 0
for mask in range(1 << n):
worker = bin(mask).count('1') # Current worker index
if worker >= n: continue
for task in range(n):
if mask >> task & 1: continue # Task already assigned
dp[mask | (1 << task)] = min(dp[mask | (1 << task)], dp[mask] + cost[worker][task])
return dp[(1 << n) - 1]
10. Fast Exponentiation (Binary Lifting)
def fast_power(base, exp, mod):
"""Compute base^exp mod mod in O(log exp)."""
result = 1
base %= mod
while exp > 0:
if exp & 1: result = result * base % mod
base = base * base % mod
exp >>= 1
return result
# Matrix exponentiation — compute M^n in O(k^3 × log n) where k = matrix size
def mat_mul(A, B, mod):
k = len(A)
C = [[0]*k for _ in range(k)]
for i in range(k):
for j in range(k):
for l in range(k):
C[i][j] = (C[i][j] + A[i][l] * B[l][j]) % mod
return C
def mat_pow(M, n, mod):
k = len(M)
result = [[1 if i==j else 0 for j in range(k)] for i in range(k)] # Identity
while n > 0:
if n & 1: result = mat_mul(result, M, mod)
M = mat_mul(M, M, mod)
n >>= 1
return result
# Use case: Fibonacci(n) in O(log n) using matrix exponentiation
def fibonacci(n, mod=10**9+7):
if n <= 1: return n
M = [[1,1],[1,0]]
result = mat_pow(M, n-1, mod)
return result[0][0]
Technique Recognition Guide
| Problem Statement Contains | Technique |
|---|
| "Find pair with sum X in sorted array" | Two pointers |
| "Longest/shortest subarray with property" | Sliding window |
| "Minimum feasible X" | Binary search on answer |
| "Sum of elements in range [l,r]" | Prefix sums |
| "Add X to all elements in range [l,r]" | Difference array |
| "Minimum/maximum in range [l,r], static" | Sparse table |
| "Values up to 10^9 but few distinct" | Coordinate compression |
| "Answer can be huge, output modulo P" | Modular arithmetic |
| "Subset of n ≤ 20 elements, optimize" | Bitmask DP |
| "Count paths of length n in graph" | Matrix exponentiation |
Summary
These 10 techniques solve a large fraction of Codeforces problems up to difficulty 1800. Master them in order:
- Two pointers — fundamental O(n) technique for sorted arrays
- Sliding window — fundamental for subarray problems
- Binary search on answer — transforms many problems from hard to easy
- Prefix sums — O(1) range queries after preprocessing
- Difference array — range updates in O(1)
- Sparse table — O(1) static range min/max queries
- Coordinate compression — handles large but sparse value ranges
- Modular arithmetic — required whenever answer is "output mod P"
- Bitmask DP — subset-based dynamic programming
- Matrix exponentiation — fast computation of Fibonacci-like recurrences
*Next Lesson: Fast Input/Output*