Chapter 7.2: Problem-Solving Strategies
Knowing algorithms is necessary but not sufficient. You also need to know how to think when facing a problem you've never seen before. This chapter teaches you a systematic approach.
7.2.1 How to Read a Competitive Programming Problem
USACO problems follow a consistent structure. Learn to parse it efficiently.
Problem Structure
- Story/Setup ā a theme (usually cows š). Mostly flavor text ā don't get distracted.
- Task/Objective ā the actual question. Read this very carefully.
- Input format ā how to read the data.
- Output format ā exactly what to print.
- Sample input/output ā the examples.
- Constraints ā the most important section for algorithm choice.
Reading Discipline
Step 1: Read the task/objective first. Then read input/output format. Step 2: Read the constraints. These tell you:
- N ⤠20 ā maybe O(2^N) or O(N!)
- N ⤠1000 ā probably O(N²) or O(N² log N)
- N ⤠10^5 ā must be O(N log N) or O(N)
- N ⤠10^6 ā must be O(N) or O(N log N)
- Values up to 10^9 ā might need
long long - Values up to 10^18 ā definitely
long long
Step 3: Work through the sample manually. Verify your understanding.
Step 4: Look for hidden constraints. "All values are distinct." "The graph is a tree." "N is even." These often unlock simpler solutions.
7.2.2 Identifying the Algorithm Type
After reading the problem, ask yourself these questions in order:
Visual: Problem-Solving Flowchart
The flowchart above captures the complete contest workflow. The key step is mapping input constraints to algorithm complexity ā use the complexity table below to make that decision quickly.
Visual: Complexity vs Input Size
This reference table tells you immediately whether your chosen algorithm will pass. If N = 10ⵠand you have an O(N²) solution, it will TLE. This table should be your first mental check when designing an approach.
Question 1: Can I brute force it?
- If N ⤠15, brute force all subsets: O(2^N)
- If N ⤠8, try all permutations: O(N!)
- Even if brute force is too slow for full credit, it's good for partial credit and for verifying your correct solution
Question 2: Does it involve a grid or graph?
- Grid with shortest path question ā BFS
- Grid/graph with connectivity ā DFS or Union-Find
- Graph with weighted edges, shortest path ā Dijkstra (Gold topic)
- Tree structure ā Tree DP or LCA
Question 3: Does it involve sorted data?
- Finding closest elements ā Sort + adjacent scan
- Range queries ā Binary search or prefix sums
- "Can we achieve value X?" type question ā Binary search on answer
Question 4: Does it involve optimal decisions over a sequence?
- "Maximum/minimum cost path" ā DP
- "Maximum number of non-overlapping intervals" ā Greedy
- "Minimum operations to transform X to Y" ā BFS (if small state space) or DP
Question 5: Does it involve counting?
- Counting subsets ā Bitmask DP (if small N) or combinatorics
- Counting paths in a DAG ā DP
- Frequency of elements ā Hash map
The Algorithm Decision Tree
Is N ⤠20?
āāā YES ā Try brute force (O(2^N) or O(N!))
āāā NO
Is it a graph/grid problem?
āāā YES
ā Is it about shortest path?
ā āāā YES (unweighted) ā BFS
ā āāā YES (weighted) ā Dijkstra (Gold)
ā āāā NO (connectivity) ā DFS / Union-Find
āāā NO
Does sorting help?
āāā YES ā Sort + scan / binary search
āāā NO
Does it have "overlapping subproblems"?
āāā YES ā Dynamic Programming
āāā NO ā Greedy / simulation
7.2.3 Testing with Examples
Always Test the Given Examples First
Before submitting, verify your solution produces exactly the right output for all provided examples.
# Compile
g++ -o sol solution.cpp -std=c++17
# Test with sample input
echo "5
3 1 4 1 5" | ./sol
# Or from file
./sol < sample.in
Create Your Own Test Cases
The provided examples are easy. Create:
- Minimum case: N=1, N=0, empty input
- Maximum case: N at max constraint, all values at max
- All same values: N elements all equal
- Already sorted / reverse sorted
- Special structures: Complete graph, path graph, star graph (for graph problems)
Stress Testing
Write a brute-force solution for small N, then compare against your optimized solution on random inputs:
// brute.cpp ā simple O(N^3) solution
// sol.cpp ā your O(N log N) solution
// stress_test.sh:
for i in {1..1000}; do
# Generate random test
python3 gen.py > test.in
# Run both solutions
./brute < test.in > expected.out
./sol < test.in > got.out
# Compare
if ! diff -q expected.out got.out > /dev/null; then
echo "MISMATCH on test $i"
cat test.in
break
fi
done
echo "All tests passed!"
Stress testing catches subtle bugs that sample cases miss.
7.2.4 Debugging Tips for C++
Strategy 1: Print Everything
When something's wrong, add cerr statements to trace your program's execution. cerr goes to standard error (separate from standard output):
cerr << "At node " << u << ", dist = " << dist[u] << "\n";
cerr << "Array state: ";
for (int x : arr) cerr << x << " ";
cerr << "\n";
Why
cerrnotcout?coutgoes to standard output where the judge checks your answer.cerrgoes to standard error, which the judge usually ignores. So your debug output doesn't pollute your answer.
Strategy 2: Use assert for Invariants
assert(n >= 1 && n <= 100000); // crashes with a message if condition fails
assert(dist[v] >= 0); // check BFS invariant
Strategy 3: Check Array Bounds
Common out-of-bounds patterns:
int arr[100];
arr[100] = 5; // Bug! Valid indices are 0-99
// Use this to detect bounds issues while debugging:
// Compile with -fsanitize=address (AddressSanitizer)
// g++ -fsanitize=address,undefined -o sol sol.cpp
Strategy 4: Rubber Duck Debugging
Explain your code line by line, out loud or in writing. The act of explaining forces you to notice inconsistencies. Many bugs are found this way ā not by staring at the screen, but by articulating what each line is supposed to do.
Strategy 5: Reduce the Problem
If your code fails on a large input, manually create the smallest input that still fails. Fix that. Repeat.
Strategy 6: Read Compiler Warnings
g++ -Wall -Wextra -o sol sol.cpp
The -Wall -Wextra flags enable all warnings. Read them! Uninitialized variables, unused variables, signed/unsigned mismatches ā all common USACO bugs.
7.2.5 USACO-Specific Debugging
Check Your I/O
The #1 cause of Wrong Answer on correct algorithms: wrong input/output format.
- Did you read the right number of values?
- Are you printing the right number of lines?
- Is there a trailing space or missing newline?
Test Timing
To check if your solution is fast enough:
time ./sol < large_input.in
USACO typically allows 2ā4 seconds. If your solution takes 10 seconds locally, it'll time out.
Estimate Complexity First
Before coding, calculate: "My algorithm is O(N²). N = 10^5. That's 10^10 operations. Way too slow."
Rough guide for what runs in 1 second with C++:
- 10^8 simple operations
- 10^7 complex operations (like map lookups)
- 10^5 Ć 10^3 = 10^8 for nested loops with simple body
7.2.6 From Bronze to Silver Checklist
Use this checklist to evaluate your readiness for Silver:
Algorithms to Know
- Prefix sums (1D and 2D)
- Binary search (including on the answer)
- BFS and DFS on graphs and grids
- Union-Find (DSU)
- Sorting with custom comparators
- Basic DP (1D DP, 2D DP, knapsack)
-
STL:
map,set,priority_queue,vector,sort
Problem-Solving Skills
- Can identify whether a problem needs BFS vs. DFS vs. DP vs. Greedy
- Can implement BFS from scratch in 10 minutes
- Can implement DSU from scratch in 5 minutes
- Can model grid problems as graphs
- Knows how to binary search on the answer
- Comfortable with 2D arrays and grid traversal
Contest Skills
- Can write a clean template with fast I/O in 30 seconds
-
Never forget
long longwhen needed - Always test with sample cases before submitting
- Can read and understand constraints quickly
- Has practiced at least 20 Bronze problems
- Has solved at least 5 Silver problems (even with hints)
Practice Plan
- Solve all easily available USACO Bronze problems (2016ā2024)
- For each problem you can't solve in 2 hours: read editorial, implement from scratch
- After solving 30+ Bronze problems, attempt Silver: start with 2016ā2018 Silver
- Keep a problem log: problem name, techniques used, key insight
7.2.7 Resources
Official
- USACO website: usaco.org ā contest archive, editorials
- USACO training: train.usaco.org ā old but good structured curriculum
Unofficial
- USACO Guide: usaco.guide ā excellent community-written guide, highly recommended
- Codeforces: codeforces.com ā more problems and contests
- AtCoder: atcoder.jp ā high-quality educational problems
Books
- Competitive Programmer's Handbook by Antti Laaksonen ā free PDF, excellent
- Introduction to Algorithms (CLRS) ā the bible for theory (heavy reading)
Chapter Summary
š Key Takeaways
| Skill | Practice Until... |
|---|---|
| Reading | Understand the problem within 3 minutes |
| Algorithm ID | Guess the right approach 70%+ of the time |
| Implementation | Finish standard problems in ā¤30 minutes |
| Debugging | Locate and fix bugs within 30 minutes |
| Testing | Develop the habit of testing edge cases before submitting |
š§© "Problem-Solving Mindset" Quick Checklist
| Step | Question to Ask Yourself |
|---|---|
| 1. Check N range | N ⤠20 ā brute force/bitmask; N ⤠10^5 ā O(N log N) |
| 2. Graph/grid? | Yes ā BFS/DFS/DSU |
| 3. Optimize a value? | "maximize minimum" or "minimize maximum" ā binary search on answer |
| 4. Overlapping subproblems? | Yes ā DP |
| 5. Sort then greedy? | Yes ā Greedy |
| 6. Range queries? | Yes ā prefix sum / segment tree |
ā FAQ
Q1: What to do when you encounter a completely unfamiliar problem type?
A: ā First write a brute force for small data to get partial credit; ā” Draw diagrams, manually compute small examples to find patterns; ⢠Try simplifying the problem (if 2D, think about the 1D version first); ⣠If still stuck, move to the next problem and come back later.
Q2: How to improve "problem recognition" ability?
A: Deliberate categorized practice. After each problem, record its "tags" (BFS, DP, greedy, binary search, etc.). After enough practice, you'll immediately associate similar constraints and keywords with the right algorithm. The Pattern Cheat Sheet in Chapter 7.1 of this book is a good starting point.
Q3: In a contest, should you write brute force first or go straight to the optimal solution?
A: Write brute force first. Brute force code usually takes only 5 minutes and serves three purposes: ā gets partial credit; ā” helps you understand the problem; ⢠can be used for stress testing to verify the optimal solution. Even if you're confident in your solution, it's recommended to write brute force first.
Q4: How to use stress testing for efficient debugging?
A: Write three programs:
brute.cpp(correct brute force),sol.cpp(your optimized solution),gen.cpp(random data generator). Run them in a loop and compare outputs. When a discrepancy is found, that small test case is your debugging clue. This is the most powerful debugging technique in competitive programming.
š Connections to Other Chapters
- The algorithm decision tree in this chapter covers the core algorithms from all chapters in this book
- Chapter 7.1 covers USACO contest rules and problem categories; this chapter covers "how to solve problems"
- The Bronze-to-Silver Checklist summarizes all knowledge points from Chapters 2.1ā6.3
- The Stress Testing technique in this chapter can be applied to Practice Problems in all chapters
The journey from Bronze to Silver is about volume of practice combined with deliberate reflection. After each problem you solve ā or fail to solve ā ask: "What was the key insight? How do I recognize this type faster next time?"
Good luck, and enjoy the cows. š