| # | Section | Page |
|---|---|---|
| 1 | General Problem-Solving Strategy | 2 |
| 2 | Last-Minute Contest Checklist | 2 |
| 3 | Input/Output Pitfalls | 3 |
| 4 | Typical Constraints → Algorithm Choice | 3 |
| 5 | Time Complexity Cheat Sheet | 4 |
| 6 | How to Recognize Problem Types | 4 |
| 7 | Algorithm Selection Guide | 5 |
| 8 | Common Data Structures | 6 |
| 9 | Graph Algorithms | 8 |
| 10 | Dynamic Programming Patterns | 12 |
| 11 | Greedy Strategies | 15 |
| 12 | Binary Search Patterns | 16 |
| 13 | Math for Competitive Programming | 17 |
| 14 | Number Theory | 18 |
| 15 | Combinatorics | 20 |
| 16 | Geometry Basics | 21 |
| 17 | String Algorithms | 23 |
| 18 | Bit Manipulation Tricks | 25 |
| 19 | Common Implementation Patterns | 26 |
| 20 | Common ICPC Tricks | 27 |
| 21 | Debugging During Contests | 28 |
| 22 | Edge Case Checklist | 29 |
The First 5 Minutes (per problem):
- Read carefully — underline constraints, output format, special cases
- Identify the problem type — see §6 Recognition Guide
- Check constraints — see §4 to pick the right algorithm class
- Think of the simplest approach first — brute force, then optimize
- Write on paper before coding — sketch the algorithm, edge cases
During Implementation:
- Code in small testable pieces; compile/test after each function
- Use simple variable names consistently (n, m, adj, dist, dp)
- Don't optimize prematurely — get a correct solution first
- If stuck > 15 min, switch problems
Penalty Minimization:
- Solve easy problems first (read all problems in first 10 min)
- One person codes, others read and think
- Test before submitting — use sample cases + 1 edge case
- Wrong Answer > re-read the problem statement
□ Compiler flags: g++ -O2 -std=c++17 -Wall
□ Template ready (fast I/O, includes, typedefs)
□ Know your judge's stack size (recursion limit)
□ Coordinate roles: coder / thinker / tester
□ Print reference sheets
□ Extra pens, paper, rubber duck
Fast I/O template (C++):
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
// ...
}Fast I/O template (Java):
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());| Pitfall | Fix |
|---|---|
| Slow I/O (TLE with cin/cout) | ios::sync_with_stdio(false); cin.tie(nullptr); |
| Mixing scanf/printf with cin/cout | Pick one. Don't mix. |
| Not reading until EOF |
while(cin >> n) or while(scanf("%d",&n)!=EOF)
|
| Trailing whitespace in output | Check judge's exact format |
\n vs endl
|
Use \n — endl flushes and is slow |
| Multiple test cases — not resetting | Clear all global arrays/vectors between cases |
| 32-bit overflow | Use long long when |
| Off-by-one in 0-indexed vs 1-indexed | Decide once, stay consistent |
| Forgetting to read remaining input | Read full line after nextInt() in Java |
| Constraint on N | Max Operations ≈ | Viable Algorithms |
|---|---|---|
|
|
Brute force, permutations, bitmask over all subsets | |
|
|
Bitmask DP, meet-in-the-middle ( |
|
|
|
Bitmask DP (tight), meet-in-the-middle | |
|
|
Floyd-Warshall, matrix exponentiation, |
|
|
|
|
|
|
|
|
|
|
|
Sorting, binary search, segment trees, BIT | |
|
|
Linear scan, hashing, two pointers, suffix array | |
| Sieve, linear DP, prefix sums | ||
|
|
Math, binary exponentiation, matrix exponentiation |
Rule of thumb: ~$10^8$ simple operations per second in C++.
| Complexity | Name | Typical Use |
|---|---|---|
| Constant | Formula, hash lookup | |
| Logarithmic | Binary search, exponentiation | |
| Square root | Factorization, block decomposition | |
| Linear | Single pass, counting | |
| Linearithmic | Sorting, merge sort, segment tree queries | |
| Quadratic | Nested loops, simple DP | |
| Sorting + quadratic | ||
| Cubic | Floyd-Warshall, matrix multiply | |
| Exponential | Subsets, bitmask DP | |
| Factorial | Permutations |
Master Theorem (for $T(n)=aT(n/b)+O(n^d)$):
- If
$d < \log_b a$ :$O(n^{\log_b a})$ - If
$d = \log_b a$ :$O(n^d \log n)$ - If
$d > \log_b a$ :$O(n^d)$
| If you see... | Think about... |
|---|---|
| "Minimum/maximum of something" | DP, greedy, binary search on answer |
| "Count the number of ways" | DP, combinatorics, math |
| "Shortest path" | BFS (unweighted), Dijkstra, Bellman-Ford |
| "Connected components" | DFS/BFS, Union-Find |
| "Can you do X in at most K?" | Binary search on answer |
| "Lexicographically smallest" | Greedy (take smallest valid choice) |
| "Subsequence" | DP (LIS, LCS), two pointers |
| "Substring / pattern matching" | KMP, Z-function, hashing |
| "All pairs of …" | Floyd-Warshall, BFS from each node |
| "Tree + queries" | LCA, Euler tour, HLD |
| "Interval/range queries" | Segment tree, BIT, sparse table |
| "Grid with obstacles" | BFS, DP on grid |
| "Matching / assignment" | Bipartite matching, Hungarian |
| "Flow / cut" | Max-flow (Dinic's) |
| "Modular arithmetic, large N" | Matrix exponentiation, Fermat's little theorem |
| "XOR of elements" | Trie on bits, linear basis |
| "GCD / LCM" | Euclidean algorithm, prime factorization |
| "Geometry: intersections" | Cross product, sweep line |
| "Game theory, winning/losing" | Sprague-Grundy, Nim |
| "String periodicity" | KMP failure function, Z-array |
| "$N \leq 20$, minimize over subsets" | Bitmask DP |
| "Online queries, dynamic updates" | Segment tree with lazy, BIT |
Given a problem category, pick the technique:
| Scenario | Algorithm | Complexity |
|---|---|---|
| Sorted array, find element | Binary search | |
| Unsorted, find element | Linear scan or hash set |
|
| Find kth smallest | Quickselect / nth_element |
|
| Answer is monotonic | Binary search on answer |
| Scenario | Algorithm | Complexity |
|---|---|---|
| General sort |
std::sort (introsort) |
|
| Stable sort needed | std::stable_sort |
|
| Integers in range [0, K] | Counting sort | |
| Custom ordering / events | Sort by key + sweep |
| Scenario | Algorithm | Complexity |
|---|---|---|
| Unweighted graph | BFS | |
| Non-negative weights | Dijkstra (priority queue) | |
| Negative weights, no neg cycle | Bellman-Ford | |
| All-pairs, |
Floyd-Warshall | |
| DAG | Topological sort + relaxation | |
| 0-1 weights | 0-1 BFS (deque) |
| Scenario | Algorithm | Complexity |
|---|---|---|
| Dense graph | Prim's |
|
| Sparse graph | Kruskal's (sort + DSU) |
- When: Connected components, Kruskal's MST, dynamic connectivity
-
Complexity:
$O(\alpha(N))$ per operation (nearly constant) - Key idea: Path compression + union by rank
int par[MAXN], rnk[MAXN];
void init(int n){ iota(par,par+n,0); fill(rnk,rnk+n,0); }
int find(int x){ return par[x]==x ? x : par[x]=find(par[x]); }
bool unite(int a, int b){
a=find(a); b=find(b);
if(a==b) return false;
if(rnk[a]<rnk[b]) swap(a,b);
par[b]=a; if(rnk[a]==rnk[b]) rnk[a]++;
return true;
}- Mistakes: Forgetting to call
find()before comparing; not initializing parent array.
- When: Prefix sum queries + point updates; count inversions
-
Complexity:
$O(\log N)$ per query/update - Key idea: Each index stores partial sum based on lowest set bit
int bit[MAXN];
void update(int i, int delta, int n){
for(; i<=n; i+=i&(-i)) bit[i]+=delta;
}
int query(int i){
int s=0; for(; i>0; i-=i&(-i)) s+=bit[i]; return s;
}
int query(int l, int r){ return query(r)-query(l-1); } // 1-indexed- Mistakes: Using 0-indexed (BIT is 1-indexed); forgetting to clear between test cases.
- When: Range queries + range/point updates (sum, min, max, GCD)
-
Complexity:
$O(\log N)$ per operation;$O(N)$ build - Key idea: Divide-and-conquer on intervals, store answers in nodes
int tree[4*MAXN], lazy[4*MAXN];
void build(int v, int tl, int tr, int a[]){
if(tl==tr){ tree[v]=a[tl]; return; }
int tm=(tl+tr)/2;
build(2*v,tl,tm,a); build(2*v+1,tm+1,tr,a);
tree[v]=tree[2*v]+tree[2*v+1];
}
void push(int v){
if(lazy[v]){
tree[2*v]+=lazy[v]; lazy[2*v]+=lazy[v];
tree[2*v+1]+=lazy[v]; lazy[2*v+1]+=lazy[v];
lazy[v]=0;
}
}
void update(int v, int tl, int tr, int l, int r, int val){
if(l>tr||r<tl) return;
if(l<=tl&&tr<=r){ tree[v]+=val; lazy[v]+=val; return; }
push(v); int tm=(tl+tr)/2;
update(2*v,tl,tm,l,r,val); update(2*v+1,tm+1,tr,l,r,val);
tree[v]=tree[2*v]+tree[2*v+1];
}
int query(int v, int tl, int tr, int l, int r){
if(l>tr||r<tl) return 0;
if(l<=tl&&tr<=r) return tree[v];
push(v); int tm=(tl+tr)/2;
return query(2*v,tl,tm,l,r)+query(2*v+1,tm+1,tr,l,r);
}- Mistakes: Array size
4*Nnot2*N; forgetting lazy propagation push; wrong merge function.
- When: Static range minimum/maximum queries (no updates)
-
Complexity:
$O(N \log N)$ build,$O(1)$ query - Key idea: Precompute answers for every power-of-2 length interval
int sp[MAXN][LOG], lg[MAXN];
void build(int a[], int n){
lg[1]=0; for(int i=2;i<=n;i++) lg[i]=lg[i/2]+1;
for(int i=0;i<n;i++) sp[i][0]=a[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=0;i+(1<<j)-1<n;i++)
sp[i][j]=min(sp[i][j-1], sp[i+(1<<(j-1))][j-1]);
}
int query(int l, int r){
int k=lg[r-l+1];
return min(sp[l][k], sp[r-(1<<k)+1][k]);
}| Structure | Use Case | Complexity |
|---|---|---|
priority_queue |
Dijkstra, greedy, K largest |
|
set / multiset
|
Ordered elements, lower_bound | |
unordered_map |
Fast lookups (hash) |
|
deque |
0-1 BFS, sliding window |
|
| Trie | Prefix queries, XOR max |
|
| Monotone stack | Next greater element, histogram | |
| Monotone deque | Sliding window min/max |
BFS — unweighted shortest path, level-order traversal
vector<int> dist(n, -1);
queue<int> q;
dist[s]=0; q.push(s);
while(!q.empty()){
int u=q.front(); q.pop();
for(int v : adj[u]) if(dist[v]==-1){
dist[v]=dist[u]+1; q.push(v);
}
}DFS — cycle detection, topological sort, connected components
vector<bool> vis(n, false);
void dfs(int u){
vis[u]=true;
for(int v : adj[u]) if(!vis[v]) dfs(v);
}- Mistakes: Stack overflow on deep DFS → use iterative; not marking visited before pushing in BFS (causes duplicates).
- When: Single-source shortest path, non-negative weights
-
Complexity:
$O((V+E) \log V)$
vector<long long> dist(n, LLONG_MAX);
priority_queue<pair<long long,int>, vector<pair<long long,int>>, greater<>> pq;
dist[s]=0; pq.push({0, s});
while(!pq.empty()){
auto [d, u] = pq.top(); pq.pop();
if(d > dist[u]) continue;
for(auto [v, w] : adj[u])
if(dist[u]+w < dist[v]){
dist[v]=dist[u]+w;
pq.push({dist[v], v});
}
}- Mistakes: Using
intwhen weights can overflow; not skipping stale entries; negative edges.
- When: Negative weights, detect negative cycles
-
Complexity:
$O(VE)$
vector<long long> dist(n, LLONG_MAX);
dist[s]=0;
for(int i=0; i<n-1; i++)
for(auto& [u,v,w] : edges)
if(dist[u]!=LLONG_MAX && dist[u]+w<dist[v])
dist[v]=dist[u]+w;
// Check for negative cycle:
for(auto& [u,v,w] : edges)
if(dist[u]!=LLONG_MAX && dist[u]+w<dist[v])
// negative cycle reachable-
When: All-pairs shortest path,
$V \leq 500$ -
Complexity:
$O(V^3)$
// dist[i][j] initialized to edge weights, INF if no edge, 0 on diagonal
for(int k=0;k<n;k++)
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
dist[i][j] = min(dist[i][j], dist[i][k]+dist[k][j]);- Mistakes: Wrong loop order (k must be outermost); overflow when adding INF+INF.
- When: DAG ordering, prerequisite scheduling, DP on DAG
-
Complexity:
$O(V+E)$
vector<int> indeg(n, 0), order;
for(int u=0;u<n;u++) for(int v:adj[u]) indeg[v]++;
queue<int> q;
for(int i=0;i<n;i++) if(indeg[i]==0) q.push(i);
while(!q.empty()){
int u=q.front(); q.pop(); order.push_back(u);
for(int v:adj[u]) if(--indeg[v]==0) q.push(v);
}
// if order.size() < n → cycle exists- When: Directed graph decomposition, 2-SAT
-
Complexity:
$O(V+E)$
int idx=0, scc_cnt=0, stk_top=0;
int disc[MAXN], low[MAXN], comp[MAXN], stk[MAXN];
bool on_stk[MAXN];
void tarjan(int u){
disc[u]=low[u]=idx++;
stk[stk_top++]=u; on_stk[u]=true;
for(int v:adj[u]){
if(disc[v]==-1){ tarjan(v); low[u]=min(low[u],low[v]); }
else if(on_stk[v]) low[u]=min(low[u],disc[v]);
}
if(low[u]==disc[u]){
while(true){
int v=stk[--stk_top]; on_stk[v]=false;
comp[v]=scc_cnt;
if(v==u) break;
}
scc_cnt++;
}
}- When: Find critical edges/nodes in an undirected graph
-
Complexity:
$O(V+E)$
int timer=0, tin[MAXN], low[MAXN];
bool vis[MAXN];
void dfs(int u, int parent){
vis[u]=true; tin[u]=low[u]=timer++;
for(int v:adj[u]){
if(v==parent) continue;
if(vis[v]) low[u]=min(low[u],tin[v]);
else{
dfs(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>tin[u]) // (u,v) is a bridge
if(low[v]>=tin[u]) // u is an articulation point (+ root check)
}
}
}- When: Tree path queries, distance between tree nodes
-
Complexity:
$O(N \log N)$ preprocess,$O(\log N)$ query
int up[MAXN][LOG], depth[MAXN];
void dfs(int u, int p){
up[u][0]=p;
for(int i=1;i<LOG;i++) up[u][i]=up[up[u][i-1]][i-1];
for(int v:adj[u]) if(v!=p){ depth[v]=depth[u]+1; dfs(v,u); }
}
int lca(int u, int v){
if(depth[u]<depth[v]) swap(u,v);
int diff=depth[u]-depth[v];
for(int i=0;i<LOG;i++) if((diff>>i)&1) u=up[u][i];
if(u==v) return u;
for(int i=LOG-1;i>=0;i--)
if(up[u][i]!=up[v][i]){ u=up[u][i]; v=up[v][i]; }
return up[u][0];
}- Tree distance:
dist(u,v) = depth[u] + depth[v] - 2*depth[lca(u,v)]
- When: Max-flow / min-cut, bipartite matching, edge-disjoint paths
-
Complexity:
$O(V^2 E)$ , but$O(E\sqrt{V})$ for unit-capacity
struct Edge{ int to, rev; long long cap; };
vector<Edge> graph[MAXN];
int level[MAXN], iter[MAXN];
void add_edge(int from, int to, long long cap){
graph[from].push_back({to,(int)graph[to].size(),cap});
graph[to].push_back({from,(int)graph[from].size()-1,0});
}
bool bfs(int s, int t, int n){
fill(level,level+n,-1); level[s]=0;
queue<int> q; q.push(s);
while(!q.empty()){
int v=q.front(); q.pop();
for(auto& e:graph[v]) if(e.cap>0 && level[e.to]<0){
level[e.to]=level[v]+1; q.push(e.to);
}
}
return level[t]>=0;
}
long long dfs(int v, int t, long long f){
if(v==t) return f;
for(int& i=iter[v]; i<(int)graph[v].size(); i++){
Edge& e=graph[v][i];
if(e.cap>0 && level[v]<level[e.to]){
long long d=dfs(e.to,t,min(f,e.cap));
if(d>0){ e.cap-=d; graph[e.to][e.rev].cap+=d; return d; }
}
}
return 0;
}
long long max_flow(int s, int t, int n){
long long flow=0;
while(bfs(s,t,n)){
fill(iter,iter+n,0);
long long d;
while((d=dfs(s,t,LLONG_MAX))>0) flow+=d;
}
return flow;
}- When: Maximum matching in bipartite graph
-
Kuhn's:
$O(VE)$ — simpler to code -
Hopcroft-Karp:
$O(E\sqrt{V})$ — faster
Kuhn's (simple):
int match_to[MAXN]; // match_to[v] = u or -1
bool used[MAXN];
bool dfs(int u){
for(int v : adj[u]) if(!used[v]){
used[v]=true;
if(match_to[v]==-1 || dfs(match_to[v])){
match_to[v]=u; return true;
}
}
return false;
}
// Call: for each u in left side, reset used[], if dfs(u) → matching++↓ Pattern: Linear DP
dp[i]= answer using first i elements- Examples: LIS, coin change, house robber
↓ Pattern: 2D DP
dp[i][j]= answer for subproblem (i, j)- Examples: LCS, edit distance, matrix chain multiplication, knapsack
↓ Pattern: Interval DP
dp[i][j]= answer for the range [i, j]- Examples: Matrix chain, burst balloons, palindrome partitioning
- Enumerate over split point k in [i, j]
↓ Pattern: Bitmask DP
-
dp[mask]ordp[mask][i]where mask is a bitmask of visited/chosen set - Examples: TSP, assignment problem, Hamiltonian path
-
$N \leq 20$ (sometimes 24 with optimizations)
↓ Pattern: Digit DP
- Count numbers in [L, R] with some digit property
- State:
dp[pos][tight][leading_zero][...] - Process digit by digit from MSB to LSB
↓ Pattern: DP on Trees
- DFS-based, compute dp for subtrees bottom-up
dp[v]depends ondp[children]- Often used with rerooting technique for "answer for each node as root"
↓ Pattern: DP on DAG
- Topological sort, then process in order
- Shortest/longest path on DAG
-
Complexity:
$O(N \log N)$ -
Key idea: Maintain array
tails[]wheretails[i]= smallest tail element for IS of lengthi+1
vector<int> tails;
for(int x : a){
auto it = lower_bound(tails.begin(), tails.end(), x);
if(it == tails.end()) tails.push_back(x);
else *it = x;
}
int lis_length = tails.size();- For non-decreasing: use
upper_boundinstead oflower_bound.
0/1 Knapsack (
int dp[MAXW+1] = {};
for(int i=0; i<n; i++)
for(int w=W; w>=weight[i]; w--)
dp[w] = max(dp[w], dp[w-weight[i]] + value[i]);Unbounded Knapsack:
for(int i=0; i<n; i++)
for(int w=weight[i]; w<=W; w++)
dp[w] = max(dp[w], dp[w-weight[i]] + value[i]);- Mistakes: Loop direction matters (0/1 = reverse; unbounded = forward).
-
Complexity:
$O(NM)$
// dp[i][j] = edit distance of a[0..i-1] and b[0..j-1]
for(int i=0;i<=n;i++) dp[i][0]=i;
for(int j=0;j<=m;j++) dp[0][j]=j;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
dp[i][j] = (a[i-1]==b[j-1]) ? dp[i-1][j-1]
: 1 + min({dp[i-1][j], dp[i][j-1], dp[i-1][j-1]});-
When:
$N \leq 20$ , visit all nodes exactly once, minimize cost -
Complexity:
$O(2^N \cdot N^2)$
// dp[mask][i] = min cost to visit nodes in mask, ending at i
memset(dp, 0x3f, sizeof dp);
dp[1<<start][start] = 0;
for(int mask=1; mask<(1<<n); mask++)
for(int u=0; u<n; u++) if(mask>>u&1)
for(int v=0; v<n; v++) if(!(mask>>v&1))
dp[mask|(1<<v)][v] = min(dp[mask|(1<<v)][v], dp[mask][u]+cost[u][v]);-
When: Compute
$F(N)$ where$F$ follows a linear recurrence and$N \leq 10^{18}$ -
Complexity:
$O(K^3 \log N)$ where$K$ = recurrence order
typedef vector<vector<long long>> Matrix;
Matrix mult(const Matrix& A, const Matrix& B, long long mod){
int n=A.size();
Matrix C(n, vector<long long>(n, 0));
for(int i=0;i<n;i++) for(int k=0;k<n;k++) if(A[i][k])
for(int j=0;j<n;j++)
C[i][j]=(C[i][j]+A[i][k]*B[k][j])%mod;
return C;
}
Matrix mat_pow(Matrix A, long long p, long long mod){
int n=A.size();
Matrix R(n, vector<long long>(n, 0));
for(int i=0;i<n;i++) R[i][i]=1; // identity
for(; p>0; p>>=1){ if(p&1) R=mult(R,A,mod); A=mult(A,A,mod); }
return R;
}Fibonacci: $\begin{pmatrix} F_{n+1} \ F_n \end{pmatrix} = \begin{pmatrix} 1 & 1 \ 1 & 0 \end{pmatrix}^n \begin{pmatrix} 1 \ 0 \end{pmatrix}$
- When: For each mask, compute sum over all submasks
-
Complexity:
$O(N \cdot 2^N)$
for(int i=0; i<n; i++)
for(int mask=0; mask<(1<<n); mask++)
if(mask >> i & 1)
dp[mask] += dp[mask ^ (1<<i)];- Exchange argument: Swapping any two elements in the optimal solution doesn't improve it
- Greedy stays ahead: The greedy choice is at least as good as any other at every step
- Matroid structure: Independence system (e.g., MST)
| Problem | Strategy |
|---|---|
| Activity selection | Sort by end time, pick non-overlapping |
| Fractional knapsack | Sort by value/weight ratio |
| Huffman coding | Merge two smallest frequencies |
| Interval cover | Sort by start, extend greedily |
| Job scheduling with deadlines | Sort by deadline, use priority queue |
| Minimum platforms | Events sorted, track overlaps |
| Task assignment | Sort both arrays, pair greedily |
Sort by some criteria
Iterate and greedily pick/assign:
if current element is valid:
take it, update state
else:
skip or adjust
- Mistakes: Assuming greedy works without proof; wrong sorting criterion; not handling ties.
int lo=0, hi=n-1, ans=-1;
while(lo<=hi){
int mid=(lo+hi)/2;
if(a[mid]>=target){ ans=mid; hi=mid-1; } // first >= target
else lo=mid+1;
}- When: "What is the minimum X such that check(X) is true?"
- Monotonicity: if
check(x)→check(x+1)(or vice versa)
long long lo=MIN_ANS, hi=MAX_ANS, ans=hi;
while(lo<=hi){
long long mid=(lo+hi)/2;
if(check(mid)){ ans=mid; hi=mid-1; } // minimize
else lo=mid+1;
}double lo=0, hi=1e18;
for(int iter=0; iter<100; iter++){ // ~100 iterations for precision
double mid=(lo+hi)/2;
if(check(mid)) hi=mid;
else lo=mid;
}| Problem | Binary Search On |
|---|---|
| Min max distance | The distance value |
| Max min value | The value threshold |
| Can partition into K groups? | The max group sum |
| Kth smallest element | The value |
- Mistakes: Integer overflow in
(lo+hi)/2→ uselo+(hi-lo)/2; wrong boundary update (infinite loop); off-by-one in lo/hi initialization.
(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 // note: +m to avoid negative
(a / b) % m = (a * b^{-1}) % m // b^{-1} = modular inverse
Modular exponentiation (fast power):
long long power(long long base, long long exp, long long mod){
long long result=1; base%=mod;
for(; exp>0; exp>>=1){
if(exp&1) result=result*base%mod;
base=base*base%mod;
}
return result;
}Modular inverse (when mod is prime):
long long modinv(long long a, long long mod){ return power(a, mod-2, mod); }- Multiplying two
long longvalues → overflow! Use__int128or modular multiply. - Negative results from subtraction: always add
modbefore taking%. MOD = 1e9+7→ write as1000000007LL.
long long gcd(long long a, long long b){ return b==0 ? a : gcd(b, a%b); }
long long lcm(long long a, long long b){ return a/gcd(a,b)*b; } // divide first to avoid overflow- Finds
$x, y$ such that$ax + by = \gcd(a, b)$
long long extgcd(long long a, long long b, long long &x, long long &y){
if(b==0){ x=1; y=0; return a; }
long long x1, y1;
long long g = extgcd(b, a%b, x1, y1);
x = y1;
y = x1 - (a/b)*y1;
return g;
}-
Complexity:
$O(N \log \log 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;Linear sieve (with smallest prime factor):
int spf[MAXN]; // smallest prime factor
vector<int> primes;
void sieve(int n){
fill(spf, spf+n+1, 0);
for(int i=2; i<=n; i++){
if(spf[i]==0){ spf[i]=i; primes.push_back(i); }
for(int p : primes){
if(p > spf[i] || (long long)i*p > n) break;
spf[i*p]=p;
}
}
}// Using smallest prime factor (after linear sieve):
map<int,int> factorize(int n){
map<int,int> f;
while(n>1){ f[spf[n]]++; n/=spf[n]; }
return f;
}
// Without sieve (O(sqrt(n))):
map<int,int> factorize(int n){
map<int,int> f;
for(int d=2; (long long)d*d<=n; d++)
while(n%d==0){ f[d]++; n/=d; }
if(n>1) f[n]++;
return f;
}int phi(int n){
int result=n;
for(int p=2; (long long)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;
}Given
// Returns (x, M) where x is the solution mod M = product of moduli
pair<long long,long long> crt(vector<long long>& a, vector<long long>& m){
long long M=1, x=0;
for(auto mi : m) M*=mi;
for(int i=0; i<(int)a.size(); i++){
long long Mi=M/m[i], yi, dummy;
extgcd(Mi, m[i], yi, dummy);
x=(x + a[i]%M * Mi%M * yi%M + M*3) % M;
}
return {x, M};
}-
$n$ has at most$O(\sqrt[3]{n})$ distinct prime factors (loose), practically ≤ 15 for$n \leq 10^{18}$ - Number of divisors of
$n$ :$d(n)$ is at most ~1500 for$n \leq 10^9$ $\sum_{i=1}^{N} \lfloor N/i \rfloor = O(N \log N)$ - Wilson's theorem:
$(p-1)! \equiv -1 \pmod{p}$ - Fermat's little theorem:
$a^{p-1} \equiv 1 \pmod{p}$ for prime$p$ ,$\gcd(a,p)=1$
Precomputation with modular inverse:
long long fac[MAXN], inv_fac[MAXN];
void precompute(int n, long long mod){
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
inv_fac[n]=power(fac[n], mod-2, mod);
for(int i=n-1;i>=0;i--) inv_fac[i]=inv_fac[i+1]*(i+1)%mod;
}
long long C(int n, int r, long long mod){
if(r<0||r>n) return 0;
return fac[n]%mod*inv_fac[r]%mod*inv_fac[n-r]%mod;
}| Identity | Formula |
|---|---|
| Pascal's | |
| Vandermonde | |
| Hockey stick | |
| Stars & bars | Distribute |
| Derangements |
|
| Catalan |
Useful for: counting multiples, derangements, surjections, chromatic polynomials.
Counts: balanced parentheses, binary trees, triangulations, non-crossing partitions, Dyck paths.
Count distinct objects under group of symmetries
Cross Product (2D):
> 0: b is counter-clockwise from a= 0: collinear< 0: b is clockwise from a
typedef long long ll;
struct P { ll x, y; };
ll cross(P O, P A, P B){ return (A.x-O.x)*(B.y-O.y)-(A.y-O.y)*(B.x-O.x); }
ll dot(P A, P B, P O={(0,0)}){ return (A.x-O.x)*(B.x-O.x)+(A.y-O.y)*(B.y-O.y); }
double dist(P A, P B){ return sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y)); }-
Complexity:
$O(N \log N)$
vector<P> convex_hull(vector<P> pts){
int n=pts.size(), k=0;
if(n<3) return pts;
sort(pts.begin(), pts.end(), [](P a, P b){ return a.x<b.x||(a.x==b.x&&a.y<b.y); });
vector<P> H(2*n);
for(int i=0;i<n;i++){
while(k>=2 && cross(H[k-2],H[k-1],pts[i])<=0) k--;
H[k++]=pts[i];
}
for(int i=n-2,t=k+1;i>=0;i--){
while(k>=t && cross(H[k-2],H[k-1],pts[i])<=0) k--;
H[k++]=pts[i];
}
H.resize(k-1);
return H;
}Two lines
// Returns true if segments (a,b) and (c,d) intersect
bool segments_intersect(P a, P b, P c, P d){
ll d1=cross(c,d,a), d2=cross(c,d,b);
ll d3=cross(a,b,c), d4=cross(a,b,d);
if(((d1>0&&d2<0)||(d1<0&&d2>0))&&((d3>0&&d4<0)||(d3<0&&d4>0))) return true;
// Check collinear cases with on-segment test if needed
return false;
}- Ray casting: Count intersections of horizontal ray with polygon edges
- Winding number: More robust for complex polygons
double polygon_area(vector<P>& p){
double area=0; int n=p.size();
for(int i=0;i<n;i++){
int j=(i+1)%n;
area += (double)p[i].x*p[j].y - (double)p[j].x*p[i].y;
}
return abs(area)/2.0;
}- Use integer arithmetic (cross products with
long long) to avoid floating point errors. - When
doubleis needed, useEPS = 1e-9for comparisons. -
Pick's theorem:
$A = I + B/2 - 1$ (lattice polygon:$I$ = interior points,$B$ = boundary points). - Angle comparison → use cross product (avoid
atan2when possible).
- When: Find all occurrences of pattern P in text T
-
Complexity:
$O(N + M)$
Failure function:
vector<int> kmp_table(const string& p){
int m=p.size();
vector<int> fail(m, 0);
for(int i=1, j=0; i<m; i++){
while(j>0 && p[i]!=p[j]) j=fail[j-1];
if(p[i]==p[j]) j++;
fail[i]=j;
}
return fail;
}Search:
vector<int> kmp_search(const string& t, const string& p){
auto fail=kmp_table(p);
vector<int> occ;
for(int i=0, j=0; i<(int)t.size(); i++){
while(j>0 && t[i]!=p[j]) j=fail[j-1];
if(t[i]==p[j]) j++;
if(j==(int)p.size()){ occ.push_back(i-j+1); j=fail[j-1]; }
}
return occ;
}- Key insight:
fail[i]= length of longest proper prefix ofp[0..i]that is also a suffix. - Period of string:
n - fail[n-1]
-
z[i]= length of longest substring starting atithat matches a prefix ofs -
Complexity:
$O(N)$
vector<int> z_function(const string& s){
int n=s.size();
vector<int> z(n, 0);
for(int i=1, l=0, r=0; i<n; i++){
if(i<r) z[i]=min(r-i, z[i-l]);
while(i+z[i]<n && s[z[i]]==s[i+z[i]]) z[i]++;
if(i+z[i]>r){ l=i; r=i+z[i]; }
}
return z;
}- Pattern matching: Compute Z-function of
P + "$" + T. Match at positioniifz[i] >= |P|.
- When: Fast substring comparison, detecting duplicates
-
Complexity:
$O(N)$ preprocess,$O(1)$ per query
const long long MOD1=1e9+7, MOD2=1e9+9, BASE1=131, BASE2=137;
long long h1[MAXN], h2[MAXN], pw1[MAXN], pw2[MAXN];
void build_hash(const string& s){
int n=s.size();
pw1[0]=pw2[0]=1; h1[0]=h2[0]=0;
for(int i=0;i<n;i++){
h1[i+1]=(h1[i]*BASE1+s[i])%MOD1;
h2[i+1]=(h2[i]*BASE2+s[i])%MOD2;
pw1[i+1]=pw1[i]*BASE1%MOD1;
pw2[i+1]=pw2[i]*BASE2%MOD2;
}
}
pair<long long,long long> get_hash(int l, int r){ // [l, r] 0-indexed
return {(h1[r+1]-h1[l]*pw1[r-l+1]%MOD1+MOD1*2)%MOD1,
(h2[r+1]-h2[l]*pw2[r-l+1]%MOD2+MOD2*2)%MOD2};
}- Use double hashing to minimize collision probability.
- Mistakes: Single hash collisions; wrong power computation; off-by-one in range.
- When: Prefix queries, autocomplete, XOR maximization
-
Complexity:
$O(L)$ per insertion/query,$L$ = string length
int trie[MAXNODES][26], cnt=0;
bool is_end[MAXNODES];
void insert(const string& s){
int u=0;
for(char c:s){
int ch=c-'a';
if(!trie[u][ch]) trie[u][ch]=++cnt;
u=trie[u][ch];
}
is_end[u]=true;
}- When: Substring searches, LCP queries, number of distinct substrings
-
Complexity:
$O(N \log N)$ build, queries vary
vector<int> suffix_array(const string& s){
int n=s.size();
vector<int> sa(n), rank_(n), tmp(n);
iota(sa.begin(), sa.end(), 0);
for(int i=0;i<n;i++) rank_[i]=s[i];
for(int k=1;k<n;k<<=1){
auto cmp=[&](int a, int b){
if(rank_[a]!=rank_[b]) return rank_[a]<rank_[b];
int ra=a+k<n?rank_[a+k]:-1, rb=b+k<n?rank_[b+k]:-1;
return ra<rb;
};
sort(sa.begin(), sa.end(), cmp);
tmp[sa[0]]=0;
for(int i=1;i<n;i++) tmp[sa[i]]=tmp[sa[i-1]]+(cmp(sa[i-1],sa[i])?1:0);
rank_=tmp;
if(rank_[sa[n-1]]==n-1) break;
}
return sa;
}LCP Array (Kasai's, $O(N)$):
vector<int> lcp_array(const string& s, const vector<int>& sa){
int n=s.size(), k=0;
vector<int> rank_(n), lcp(n-1);
for(int i=0;i<n;i++) rank_[sa[i]]=i;
for(int i=0;i<n;i++,k?k--:0){
if(rank_[i]==n-1){k=0;continue;}
int j=sa[rank_[i]+1];
while(i+k<n&&j+k<n&&s[i+k]==s[j+k]) k++;
lcp[rank_[i]]=k;
}
return lcp;
}-
Distinct substrings =
$\frac{n(n+1)}{2} - \sum \text{LCP}[i]$
| Operation | Code | Description |
|---|---|---|
| Check bit i | (n >> i) & 1 |
Is bit i set? |
| Set bit i | n | (1 << i) |
Turn on bit i |
| Clear bit i | n & ~(1 << i) |
Turn off bit i |
| Toggle bit i | n ^ (1 << i) |
Flip bit i |
| Lowest set bit | n & (-n) |
Isolate lowest 1 |
| Clear lowest set bit | n & (n-1) |
Turn off lowest 1 |
| Count set bits | __builtin_popcount(n) |
Number of 1s |
| Leading zeros | __builtin_clz(n) |
(32-bit) |
| Trailing zeros | __builtin_ctz(n) |
Position of lowest 1 |
| All bits set up to i | (1 << (i+1)) - 1 |
Mask of bits [0..i] |
| Is power of 2 | n > 0 && (n & (n-1)) == 0 |
|
| Next higher with same popcount | (see below) |
// All submasks of mask (including 0)
for(int sub = mask; sub > 0; sub = (sub-1) & mask)
// process sub
// Don't forget sub = 0 case if neededTotal iterations over all masks:
int mask = (1<<k)-1;
while(mask < (1<<n)){
// process mask
int c = mask & (-mask), r = mask + c;
mask = (((r^mask)>>2)/c) | r;
}-
$a \oplus a = 0$ ,$a \oplus 0 = a$ - XOR is associative and commutative
- Finding the element that appears once: XOR all elements
- Maximum XOR pair: use a trie on bit representation
- Use
1LL << ifori >= 31to avoid undefined behavior __builtin_popcountll()forlong long
// Pattern: Find subarray with property (e.g., sum >= target)
int l=0, best=INT_MAX;
long long sum=0;
for(int r=0; r<n; r++){
sum += a[r];
while(sum >= target){
best = min(best, r-l+1);
sum -= a[l++];
}
}// Fixed window of size k
for(int i=0; i<n; i++){
// Add a[i] to window
if(i >= k){
// Remove a[i-k] from window
}
if(i >= k-1){
// Window [i-k+1 .. i] is complete, process it
}
}vector<int> vals(a, a+n);
sort(vals.begin(), vals.end());
vals.erase(unique(vals.begin(), vals.end()), vals.end());
for(int i=0; i<n; i++)
a[i] = lower_bound(vals.begin(), vals.end(), a[i]) - vals.begin();1D:
prefix[0]=0;
for(int i=0;i<n;i++) prefix[i+1]=prefix[i]+a[i];
// sum(l,r) = prefix[r+1]-prefix[l] (0-indexed)2D:
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
pre[i][j]=a[i][j]+pre[i-1][j]+pre[i][j-1]-pre[i-1][j-1];
// sum(r1,c1,r2,c2) = pre[r2][c2]-pre[r1-1][c2]-pre[r2][c1-1]+pre[r1-1][c1-1]- Split set into two halves, enumerate both (
$2^{N/2}$ each), combine - Often used with sorting + binary search on one half
- Split array into
$\sqrt{N}$ blocks of size$\sqrt{N}$ - Query:
$O(\sqrt{N})$ , Update:$O(\sqrt{N})$ or$O(1)$ - Simpler alternative to segment tree for some problems
-
Fast I/O is mandatory — always include
ios::sync_with_stdio(false). -
Use
long longby default when constraints are unclear or products can overflow. -
Precompute factorials and inverses up to max
$N$ at start. -
const int INF = 0x3f3f3f3f — works with
memset(arr, 0x3f, sizeof arr)for int. -
For
long longINF: use0x3f3f3f3f3f3f3f3fLLwithmemset. -
__gcd(a,b)is built-in in C++ (alsostd::gcdin C++17). -
__builtin_popcount— counts set bits in constant time. -
Avoid
endl— use'\n'. - Multitest problems: Double-check all globals and data structures are reset.
- Adjacency list > matrix for sparse graphs.
-
pair<int,int>sorts lexicographically — useful for event-based problems. - Lambda comparators for custom sorting in priority queues.
-
nth_elementfor$O(N)$ kth smallest without full sort. -
Use
assert()for debugging invariants, remove before submitting. -
If TLE: Check for accidental
$O(N^2)$ in string concatenation or vector copying. - Declare large arrays globally (stack overflow prevention).
- For graph problems: always check if the graph is 0-indexed or 1-indexed.
-
Random shuffle for randomized algorithms: use
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
- Re-read the problem — 50% of WA comes from misunderstanding
- Check sample cases by hand — trace your algorithm step by step
- Print intermediate values — use stderr:
cerr << "x=" << x << endl; - Generate small random tests:
# gen.py — random test generator
import random
n = random.randint(1, 10)
print(n)
print(*[random.randint(1, 100) for _ in range(n)])# stress.sh — compare brute force vs optimized
while true; do
python3 gen.py > in.txt
./brute < in.txt > out1.txt
./sol < in.txt > out2.txt
if ! diff -q out1.txt out2.txt > /dev/null; then
echo "DIFF FOUND"; cat in.txt; break
fi
done| Bug | How to Spot |
|---|---|
| Array out of bounds | MAXN too small, 0/1 indexing confusion |
| Integer overflow | Products of values > |
| Uninitialized variables |
dp array not zeroed, dist not set to INF |
| Wrong data type |
int instead of long long
|
| Off-by-one |
< vs <=, n-1 vs n
|
| Not resetting between test cases | Global arrays, visited flags, DSU |
| Wrong sort comparator | Not a strict weak ordering → UB |
| Infinite loop | Binary search boundaries, while conditions |
| Copy-paste errors | Variable names from duplicated blocks |
Always test these before submitting:
- N = 0 or N = 1 (minimum input)
- All elements the same
- Already sorted / reverse sorted
- Maximum values (stress-test overflow)
- Negative numbers (if allowed)
- Empty graph (no edges)
- Disconnected graph
- Tree is a line (worst case depth)
- Self-loops and parallel edges (if not explicitly excluded)
- Large prime inputs (for modular problems)
- Answer is 0 or answer is 1
- Exact boundary cases:
$N = \text{max}$ , weights = 0 - Single test case vs. multi-test — did you read
T? - Floating point: values very close together, degenerate geometry
sort(a, a+n) // O(N log N)
lower_bound(a, a+n, x) // first >= x
upper_bound(a, a+n, x) // first > x
unique(a, a+n) // remove consecutive duplicates
next_permutation(a, a+n) // next lexicographic permutation
__gcd(a, b) // GCD
accumulate(a, a+n, 0LL) // sum (use 0LL for long long!)
*max_element(a, a+n) // max value
*min_element(a, a+n) // min value
const int INF = 0x3f3f3f3f; // ~1.06e9
const long long LINF = 0x3f3f3f3f3f3f3f3fLL; // ~4.56e18
const int MOD = 1e9 + 7;
const double PI = acos(-1.0);
const double EPS = 1e-9;#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> vi;
#define pb push_back
#define all(x) (x).begin(),(x).end()
#define sz(x) (int)(x).size()
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t; cin >> t;
while(t--){
// solve
}
}End of ICPC Contest Reference Sheet
Page Count Estimate: ~28 pages when printed in compact format (10pt font, narrow margins)
Version: March 2026 | Compile: g++ -O2 -std=c++17 -Wall -Wextra