Competitive Programming
Read the constraints before choosing an algorithm. They limit both the work and memory your solution can use.
Reading the bound
Small bounds may allow exhaustive search. Larger bounds often need sorting, dynamic programming or more efficient query structures. These are hints, not guarantees about the intended solution.
Check limits across all test cases as well as within one case. Runtime also depends on the language, data layout and judge hardware.
Decomposition
- Square-root decomposition splits work into blocks.
- Fenwick trees handle prefix sums; segment trees support a broader range of associative operations.
- Sparse tables support static range queries, with constant-time queries for idempotent operations such as minimum.
- Heavy-light and centroid decomposition help reduce tree queries to smaller problems.
Choose the simplest structure that meets the bound.
Amortisation and offline reasoning
An expensive operation can still be efficient overall if it happens rarely. Small-to-large merging uses this idea by moving elements into progressively larger sets.
If queries can be answered offline, sorting and sweeping may replace a more complex online structure. Persistent structures keep earlier versions available for queries.
The shapes worth recognising
For dynamic programming, define the state and transition before looking for an optimisation. Techniques such as divide-and-conquer optimisation require specific conditions.
Matching and selection problems may reduce to flow. String matching may use prefix functions, Z-functions or Aho–Corasick, depending on whether there is one pattern or many.
Constant factors that change the verdict
Once the complexity fits, check memory use, input handling and recursion depth. Flat storage and bitsets can help some workloads. Measure before adding low-level optimisations.
Practice that works
Try a problem before reading its editorial. Afterwards, implement the solution yourself. Compare against brute force on small generated inputs to catch mistakes.
A solution checklist
- State what the input represents and what the output must satisfy.
- Choose a representation and identify an invariant.
- Explain why each transition or greedy choice preserves correctness.
- Bound the time and memory over the whole input.
- Check edge cases before submitting.
A plausible pattern from small inputs is a starting point, not a proof.
Common implementation failures
Check whether arithmetic needs a wider type before multiplication happens. Confirm whether intervals are closed or half-open. Reset state between test cases and distinguish unreachable states from valid zero values.
Recursion depth can depend on input shape. A tree-shaped input can still be a long chain. Sorting comparators must satisfy the ordering rules expected by the language.
Reviewing a contest
Separate an algorithm mistake from an implementation mistake or a time-management mistake. Re-solve problems you nearly finished before moving to entirely new material. Keep short notes on the missing idea rather than copying a full editorial.
For a difficult optimisation, keep the slow correct version as a comparison tool on small inputs. See Testing and Profiling.