Most lost contests come from bugs you could not see, not from algorithms you did not know. What works: sanitizers, a stress test, and a fixed checklist, used in that order.

Compile flags

g++ -std=c++20 -O2 -DLOCAL -Wall -Wextra -Wshadow -Wconversion \
    -fsanitize=address,undefined -fno-sanitize-recover -D_GLIBCXX_DEBUG -g sol.cpp -o sol
FlagCatches
-fsanitize=addressout-of-bounds, use-after-free, stack overflow, with the exact line
-fsanitize=undefinedsigned overflow, shifts width, null deref, misaligned access
-D_GLIBCXX_DEBUGvector[i] out of range, invalid iterators, non-strict comparators
-Wshadowan inner i or n shadowing the outer one
-Wall -Wextrauninitialized variables, missing returns, signed/unsigned comparisons

Turning on sanitizers usually finds the cause of an RE or WA in under a minute. Keep this alias as your default local build.

Debug macro

#ifdef LOCAL
template<class T> void _p(const T& x);
void _p(const string& s) { cerr << '"' << s << '"'; }
void _p(char c) { cerr << '\'' << c << '\''; }
template<class A, class B> void _p(const pair<A, B>& p) { cerr << '('; _p(p.first); cerr << ", "; _p(p.second); cerr << ')'; }
template<class T> void _p(const T& x) {
    if constexpr (requires { x.begin(); }) { cerr << '{'; bool f = 1; for (auto& e : x) { if (!f) cerr << ", "; f = 0; _p(e); } cerr << '}'; }
    else cerr << x;
}
void _print() { cerr << '\n'; }
template<class T, class... V> void _print(const T& t, const V&... v) { _p(t); if (sizeof...(v)) cerr << ", "; _print(v...); }
#define dbg(...) cerr << "[" << #__VA_ARGS__ << "] = ", _print(__VA_ARGS__)
#else
#define dbg(...) 42
#endif

It prints any nested container, for example dbg(g, dist). Output goes to cerr, which the judge ignores, but it still costs time on big outputs.

assert is cheap and turns a silent WA into an RE at the exact place where an invariant breaks. Leave asserts in unless they sit in the hottest loop.

Stress testing

You need three programs: sol (fast), brute (obviously correct, exponential is fine), and gen (random small tests seeded by argv[1]).

// gen.cpp
int main(int argc, char** argv) {
    mt19937 rng(atoi(argv[1]));
    auto r = [&](int l, int h) { return int(rng() % (h - l + 1)) + l; };
    int n = r(1, 8);
    cout << n << '\n';
    for (int i = 0; i < n; i++) cout << r(1, 10) << " \n"[i == n - 1];
}
for ((i = 1; ; i++)); do
    ./gen $i > in; ./sol < in > o1; ./brute < in > o2
    if ! cmp -s o1 o2; then echo "seed $i"; cat in; echo "got:"; cat o1; echo "want:"; cat o2; break; fi
done
  • Keep the tests tiny. With and values , a bug that appears at almost always appears at , and a 6-element case can be traced by hand. Large random tests find bugs you then can’t diagnose.
  • Vary the generator’s shape: many duplicates (values in ), sorted, all equal, a path, a star, a disconnected graph, .
  • Several valid answers: write a checker that validates the output and compares only the score with the brute force.
  • No brute force: write a second, different solution (two different wrong solutions rarely agree), a validator for constructive output, assert the invariants inside the solution, compare against a slow exact DP on tiny (optimisation problems), or brute-force by enumerating all / possibilities (counting problems).
  • Shrink the failing case: drop one element or decrease one value at a time, keeping the change while it still fails.
  • TLE hunting: build worst cases on purpose: maximum , path graph (recursion depth), star (degree), all-equal (amortized structures), sorted (naive quicksort or BST), anti-hash input, and grids that break SPFA.

Verdict to likely cause

VerdictLook at
WA on test 1misread statement, output format, wrong test-case loop
WA on a later testedge cases. Stress test
WA only on big testsoverflow, uncleared globals, wrong array size
WA on test 2+ in multiteststate not reset between tests
TLEcomplexity, endl, slow I/O, map in hot loop, per-test memset of MAXN
TLE on one testadversarial worst case: SPFA, hash, recursion
MLEll table, vector<vector> overhead, persistent nodes, recursion
REout of bounds, division by zero, stack overflow, failed assert, bad comparator
Idleness limitdid not flush (interactive)
PEspaces, newlines, YES vs Yes

The checklist before submitting

  • All samples pass, including the ones in the notes section.
  • Edge cases: , if allowed, all equal, all negative, max values, disconnected, impossible answer.
  • long long wherever a product or sum can exceed . Any int * int?
  • Modulus after every + and *; subtraction normalised: (a - b + M) % M.
  • Globals and per-test structures reset (only the used part).
  • Indexing consistent; arrays sized n + 1 where needed.
  • Recursion depth fine for a path of length .
  • Output format: precision, separators, -1 sentinel, case labels.
  • Complexity and memory checked against the true worst case.

Topic-specific traps

AreaTrap
Binary searchpredicate not monotone; bounds don’t contain the answer; (l + r) / 2 with negative bounds rounds toward zero (use l + (r - l) / 2 or >> 1)
Real binary searchwhile (r - l > eps) can loop forever at ; use a fixed count (100 iterations)
Dijkstrasilently wrong with negative edges; forgetting the stale-entry check makes it in bad cases
Bridgesskip the parent edge id, not the parent vertex, or parallel edges break it
Directed cycle detectionneeds three colours
0/1 knapsackweights loop downward (upward is the unbounded knapsack)
Coin changecoins outer gives combinations; amount outer gives permutations
DPunreachable states must be , not 0
Hashingfixed base and mod is hackable; use a random base mod
Rollback DSUpath compression breaks rollback
Mo’s algorithmexpand before shrinking, or the window can go negative
Point in polygonhalf-open ranges at vertices
Geometryacos of is NaN, so clamp; prefer atan2(cross, dot)
Floating output-0.000000: add 0.0 or clamp near zero
__builtin_clz(0)undefined

When stuck

  1. Re-read the statement, especially what the samples say that the text doesn’t.
  2. Explain the algorithm out loud, line by line. The bug usually shows up mid-sentence.
  3. Print intermediate state for the smallest failing case and compare with a hand trace.
  4. Suspect the “obviously correct” parts: input reading, the reset between tests, the output.
  5. Two WAs on the same idea: stop resubmitting tweaks and stress test.
  6. For anything longer than a few minutes, a real debugger (gdb, or the IDE’s) beats print statements — a watchpoint on the variable that goes wrong finds the write directly. Print debugging wins on speed of setup, not on power.

Checklists by topic

Narrower than the general checklist above: the questions specific to each problem shape, worth running through once the general checklist passes and the bug is still there.

GraphDPBinary searchGeometry
Directed or undirected?What exactly is the state — complete and minimal?Predicate defined preciselyIntegers wherever possible; squared distances for comparisons
Weighted, unweighted, negative weights?Transitions acyclic (valid evaluation order)?Monotone — verify against a brute force if unsureOne epsilon, scaled to the coordinate magnitude
Connected or multiple components?Unreachable states , not 0?Bounds provably contain the answerDegenerate cases: collinear, duplicate points, , zero-length segments
Self-loops or parallel edges?Modulus required?First-true or last-true chosen deliberatelyOrientation convention fixed (CCW positive), polygons normalised
Tree, DAG, bipartite, or general?Max, min, count, or probability — order matters (over/under-counting)?Real-valued search uses a fixed iteration count, not an epsilonCross products checked for overflow
Is or the binding constraint?Knapsack: weight loop downward for 0/1, upward for unbounded; coins outside for combinations, amount outside for permutations——
—Can the state be compressed? Memory within limits?——