Capstone: Solve a Hard Contest Problem End-to-End
ক্যাপস্টোন — একটি কঠিন কনটেস্ট সমস্যা
1. The Workflow That Wins Contests
By Module 39 you have all the tools. The hard part is using them in the right order. Every contest problem benefits from this seven-step workflow:
- Read. Slowly. Twice.
- Restate the problem in your own words.
- Brute force first. Always have a slow-but-correct reference.
- Look for structure. Monotonicity, prefix sums, mod patterns, graph models, DP states.
- Estimate the complexity budget. n = 10⁵ → O(n log n) max.
- Implement carefully. Cleanest code wins time.
- Stress test against the brute force on random small inputs before submit.
2. The Problem We'll Solve
a[0..n−1] of integers and an integer K, find the length of the
longest contiguous subarray whose sum is divisible by K.
n ≤ 10⁵, |a[i]| ≤ 10⁹, K ≤ 10⁹.
Example: a = [4, 5, 0, −2, −3, 1], K = 5. Answer: 6 (the whole array).
3. Step 1 — Brute Force, O(n²)
Try every (l, r) pair, compute the sum (use prefix sums to make it O(1) per pair). O(n²) comparisons. Slow but a perfect reference for stress testing.
#include <bits/stdc++.h>
using namespace std;
int brute(vector<int>& a, int K) {
int n = a.size(), best = 0;
vector<long long> ps(n + 1, 0);
for (int i = 0; i < n; i++) ps[i+1] = ps[i] + a[i];
for (int l = 0; l < n; l++)
for (int r = l; r < n; r++)
if ((ps[r+1] - ps[l]) % K == 0)
best = max(best, r - l + 1);
return best;
}
int main() {
vector<int> a = {4, 5, 0, -2, -3, 1};
cout << brute(a, 5);
}
4. Step 2 — Spot the Pattern
(ps[r+1] − ps[l]) % K == 0 ⇔ ps[r+1] % K == ps[l] % K. So we need:
two prefix sums with the same residue mod K, as far apart as possible.
Walk left to right. For each residue, store the first index where it
occurred. When we see the residue again at index r, the candidate length is
r − firstIndex[residue].
ps[r+1] % K == ps[l] % K মানে আমরা মূলত মাত্র "একই residue-এর জোড়া" খুঁজছি — যা hashmap দিয়ে O(n)-এ বের হয়।
5. Step 3 — Optimal O(n) Implementation
#include <bits/stdc++.h>
using namespace std;
int optimal(vector<int>& a, int K) {
unordered_map<long long, int> first;
first[0] = -1; // empty prefix
long long sum = 0;
int best = 0;
for (int i = 0; i < (int)a.size(); i++) {
sum += a[i];
long long r = ((sum % K) + K) % K; // handle negatives
if (first.count(r)) best = max(best, i - first[r]);
else first[r] = i;
}
return best;
}
int main() {
vector<int> a = {4, 5, 0, -2, -3, 1};
cout << optimal(a, 5) << "\n"; // 6
vector<int> b = {2, 7, 6, 1, 4, 5};
cout << optimal(b, 3); // 4 (7+6+1+4=18 div by 3)
}
sum % K can be negative in C++ — normalise via ((sum % K) + K) % K.
(2) Always seed the map with first[0] = −1 for the empty prefix.
6. Step 4 — Stress Test
Generate random small inputs and compare brute vs optimal. If they ever disagree, you know instantly which one is wrong (almost always the optimal — brute force is the trusted oracle).
#include <bits/stdc++.h>
using namespace std;
// (paste brute() and optimal() from above)
int brute(vector<int>& a, int K);
int optimal(vector<int>& a, int K);
int main() {
mt19937 rng(12345);
for (int tc = 0; tc < 200; tc++) {
int n = rng() % 10 + 1;
int K = rng() % 5 + 1;
vector<int> a(n);
for (int& x : a) x = (int)(rng() % 21) - 10;
int b = brute(a, K), o = optimal(a, K);
if (b != o) {
cout << "DIFF! K=" << K << " a:";
for (int x : a) cout << " " << x;
cout << " brute=" << b << " opt=" << o << "\n";
return 1;
}
}
cout << "200 random tests passed!";
}
7. Step 5 — Submit With Confidence
With brute, optimal, and 200 random tests passing, you submit the optimal version and watch the green AC tick appear. The reasoning chain — prefix sums → mod K equivalence → hash map of first occurrences → O(n) — is the same chain you will use on dozens of variants.
8. Stretch Problems
-
Longest substring with at most K distinct characters.K-distinct char-এর longest substring।
✨ Show Answer (উত্তর দেখুন)
Approach: sliding window. Expand right, track distinct count via a frequency map. While > K, shrink left. Update max length. O(n).
-
Subarrays with sum exactly K (count of subarrays).Sum exactly K subarray-এর সংখ্যা।
✨ Show Answer (উত্তর দেখুন)
Approach: prefix sum + hash map. For each i, count[ps[i+1] − K] is the number of subarrays ending at i with sum K. Increment count[ps[i+1]] after.
-
Maximum sum rectangle in a 2D matrix.2D matrix-এ max sum rectangle।
✨ Show Answer (উত্তর দেখুন)
Approach: for each pair of rows (top, bottom), compress columns into a 1D array of column sums; run Kadane. O(R²·C).
-
Longest equal-count 0/1 subarray.সমান 0 ও 1 আছে এমন longest subarray।
✨ Show Answer (উত্তর দেখুন)
Approach: map 0 → −1, 1 → +1. Now we need longest subarray with sum 0 — same trick: hash map of first prefix-sum occurrence.
-
Jump Game V — given heights array and a max jump distance, find max number of indices visited per start. (LeetCode hard #1340.)Jump Game V (LeetCode 1340)।
✨ Show Answer (উত্তর দেখুন)
Approach: sort indices by height ascending. DP from smallest height upward: dp[i] = 1 + max over reachable lower neighbours of dp[j]. Lower-height already computed → simple recurrence. O(n·d).
What You Have Achieved
Forty modules ago, you started with the question: what is a data structure? Today, you can:
- Reason about complexity in Big-O / Θ / Ω, with Master Theorem on standby.
- Implement every classic data structure from scratch — arrays, linked lists, stacks, queues, heaps, hash tables, BSTs, AVL, Red-Black, B+ trees, segment trees, BIT, sparse tables, treaps.
- Solve graph problems with BFS, DFS, Dijkstra, Bellman-Ford, Floyd-Warshall, Kruskal, Prim, Tarjan, max-flow.
- Apply greedy, divide and conquer, dynamic programming (1D, 2D, bitmask, digit, tree DP), backtracking, and branch-and-bound.
- Pattern-match strings linearly with KMP, Z, suffix array, Aho-Corasick.
- Compute geometry primitives, primes, modular inverses, and combinatorial counts.
- Walk a Codeforces-style problem from read to AC with a stress-tested optimal solution.