← All Posts
CP Math · Contest Strategy · Part 2 of 12

Codeforces Contest Strategy

Rating on Codeforces is not purely a measure of how many algorithms you know — it is equally a measure of how efficiently you convert knowledge into accepted submissions under time pressure. I have watched contestants with deeper theoretical knowledge lose rating to faster solvers who make fewer mistakes. This post distills the meta-game: contest formats, speed techniques, problem ordering, penalty management, common pitfalls, division-specific tactics, and the post-contest habits that separate those who plateau from those who keep climbing.

Codeforces Contest Formats

Before you can optimize your contest strategy, you need to understand what you are optimizing for. Codeforces runs several distinct contest formats, each with different scoring rules, problem counts, and target audiences. Treating them identically is a common mistake.

Div. 1, Div. 2, and Combined Rounds

Div. 2 rounds target contestants rated below 1900. They typically feature 6 problems labeled A through F, with difficulty roughly spanning 800 to 2400. The scoring uses a max-point system: each problem starts with a maximum score (e.g., 500, 1000, 1500, 2000, 2500, 3000) that decays over time. The faster you solve, the more points you earn. A wrong submission costs you a 50-point penalty deducted from your final score on that problem.

Div. 1 rounds are for contestants rated 1900 and above. They also have 5–6 problems, but the easiest Div. 1 A is typically around the difficulty of a Div. 2 D or E. The scoring system is the same max-point decay model, but the stakes per problem are higher because the field is stronger and score differentials are tighter.

Combined Div. 1+2 rounds merge both pools. Problems A–C are accessible to Div. 2 participants, while D–F (or beyond) challenge Div. 1 solvers. These rounds carry rated changes for everyone, making them high-value events.

Div. 3 and Div. 4

Div. 3 (rated below 1600) and Div. 4 (rated below 1400) are beginner-friendly rounds. They typically have 7–8 problems with ICPC-style scoring: each problem is worth a fixed number of points, and the penalty is the submission time plus a per-wrong-attempt penalty (typically 10 minutes). There is no score decay — solving the problem at minute 5 or minute 115 awards the same points, but your penalty time differs.

This scoring difference is critical. In Div. 3/4, your primary goal is to maximize the number of problems solved, then minimize penalty. In Div. 1/2, solving a hard problem early can outscore solving two easy problems late.

Educational Rounds

Educational rounds are rated for contestants below 2100 and use the ICPC-style scoring with an extended 12-hour open phase after the 2-hour rated window. The problems are curated to teach specific algorithmic concepts, so the difficulty curve can feel unusual — sometimes E is easier than D for contestants who happen to know the required technique. Educational rounds also have a hack phase that opens after the rated window, allowing you to challenge other participants' solutions.

Global Rounds

Global rounds are open to everyone and are rated for all participants. They typically feature 8–9 problems with the max-point scoring system. These rounds have the highest participation counts and carry significant rating weight. The problem set spans a huge difficulty range, often from 800 to 3500+, making them valuable contests for every skill level.

Speed Solving: The First 30 Minutes

In a typical Div. 2 round, solving A and B within the first 10 minutes and C within 30 minutes puts you in a strong position. The first half-hour is where the largest rating gains (or losses) happen because max-point scoring decays rapidly and thousands of contestants are racing for the same problems.

The Fast I/O Template

Before the contest even starts, your template should be ready. Every second counts — do not waste time typing boilerplate. Here is the template I use:

#include <bits/stdc++.h>
using namespace std;

using ll  = long long;
using pii = pair<int, int>;
using vi  = vector<int>;

#define all(v)  (v).begin(), (v).end()
#define sz(v)   (int)(v).size()
#define rep(i, a, b) for (int i = (a); i < (b); ++i)

void solve() {
    // solution goes here
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--) solve();

    return 0;
}

Key details: ios::sync_with_stdio(false) and cin.tie(nullptr) together can make I/O 5–10× faster. The solve() function per test case prevents variable leakage between cases (a surprisingly common source of Wrong Answer on test case 2+). The type aliases save keystrokes when you are writing under pressure.

Reading Problems Efficiently

Speed reading is a trainable skill. When you open problem A, do not read the flavor text carefully — skim for the mathematical statement. Look for:

A common beginner mistake is reading every word of the problem narrative. Codeforces problems often wrap simple math in elaborate stories. Train yourself to extract the formal problem from the narrative in under 60 seconds for A/B problems.

Coding Fast Without Bugs

Speed and correctness are not opposites — they are correlated. The fastest way to code is to think for 30 extra seconds before typing. Decide on your approach, mentally trace through the first example, then code it in one pass. Debugging a wrong approach costs far more time than planning costs upfront.

Practical habits that improve coding speed:

Problem Ordering Strategy

The default approach — solving A, then B, then C, in order — is reasonable but not optimal. Strategic problem ordering can gain you hundreds of rating points over a season.

The "Scan All Problems" Approach

Many top-rated contestants spend the first 2–3 minutes scanning all problem statements. The goal is not to solve anything yet but to classify each problem:

Solve all "Instant" problems first, then "Familiar" ones, then consider "Uncertain" ones. Never start an "Uncertain" problem if a "Familiar" one remains unsolved — the expected value is worse.

Decision Framework for Mid-Contest

At any point during the contest, you face a decision: continue working on the current problem, or switch to another. Use this mental flowchart:

Current problem status?
├── Have a working approach, just coding → STAY (finish it)
├── Stuck for >10 minutes with no new ideas
│   ├── Unread problems remain → SWITCH (read the next one)
│   └── All problems read → revisit with fresh eyes in 5 min
├── Wrong Answer on submission
│   ├── Failing on sample test → re-read statement, then debug
│   └── Failing on hidden test
│       ├── Can generate small counter-examples → debug
│       └── No idea why it fails → SWITCH (come back later)
└── Time Left < 20 min
    └── Focus on the problem closest to submission

Time Allocation as an Optimization Problem

Think of the contest as a resource allocation problem. You have T minutes of contest time and n problems. Each problem i has an estimated probability pi of solving it and an expected time cost ti. The expected score from attempting problem i is approximately pi × scorei. You want to maximize total expected score subject to the time constraint.

This is essentially a fractional knapsack problem. Sort problems by pi × scorei / ti (expected value per minute) and work on them in that order. In practice, you estimate these quantities intuitively, but framing it this way helps you make better decisions. A problem worth 2000 points that you have a 20% chance of solving in 40 minutes yields 10 expected points per minute. A problem worth 1000 points that you will almost certainly solve in 15 minutes yields ~67 expected points per minute. Always take the latter first.

Penalty Time & Scoring Optimization

Understanding the scoring system deeply — not just superficially — can be the difference between gaining and losing rating on an otherwise identical performance.

Max-Point Scoring (Div. 1/2, Global)

In max-point rounds, each problem's score decays linearly with time. The formula is:

score = max(3p/10, p - p/250 × t - 50 × w)

where p is the max points, t is the time in minutes since contest start, and w is the number of wrong submissions. The score is floored at 30% of the max points. Key implications:

ICPC-Style Scoring (Div. 3/4, Educational)

In ICPC-style rounds, all problems are worth equal points (or ranked by difficulty: 1, 2, 3, …). Your rank is determined first by the number of problems solved, then by total penalty time. Penalty time is the sum of, for each solved problem, the submission time plus a per-wrong-attempt penalty (usually 10 minutes).

This means: never leave a solvable problem unattempted. Solving 6 problems with 300 penalty minutes always beats solving 5 problems with 0 penalty. The implication for ordering is different from max-point rounds: solve the easiest problems first to lock in low penalty times, even if they are worth fewer points in an absolute sense.

The Hack Phase

In Div. 1/2 rounds, a hack phase opens after the contest. During this phase, you can view other contestants' code and submit counter-test-cases. A successful hack awards +100 points; an unsuccessful one costs −50 points. Hacking strategy:

Submit vs. Debug Decision

A practical heuristic for when to submit vs. when to keep debugging:

Expected penalty of submitting now:
  = (1 - p_correct) × 50 points   [max-point]
  = (1 - p_correct) × 10 minutes  [ICPC-style]

Expected cost of debugging for d more minutes:
  = d × (p/250) points lost from decay  [max-point]
  = d minutes added to penalty           [ICPC-style]

Submit now if: expected penalty < expected cost of debugging

In practice, if you have tested against all sample cases and a few edge cases, your probability of being correct is typically above 80%. At that point, submitting is almost always better than spending another 5 minutes looking for bugs in max-point rounds.

Common Mistakes in Contests

Most contest rating is lost not from failing to solve hard problems but from making preventable mistakes on problems you know how to solve. Here are the most frequent offenders and how to defend against them.

Integer Overflow

The single most common source of Wrong Answer in competitive programming. If the problem involves multiplying two numbers that can each be up to 109, the product exceeds the 32-bit integer range. Always use long long when in doubt:

// WRONG: overflows when a[i] and a[j] are large
int product = a[i] * a[j];

// CORRECT: cast to long long before multiplication
ll product = (ll)a[i] * a[j];

// DEFENSIVE: use long long by default for accumulations
ll sum = 0;
for (int i = 0; i < n; ++i) {
    sum += a[i];  // safe even if a[i] is int
}

A useful rule: if the answer or any intermediate value could exceed 2 × 109, use long long. When doing modular arithmetic, be especially careful — the product of two values under 109 + 7 can overflow int.

Off-by-One Errors

Off-by-one errors are insidious because they often pass sample tests (which tend to use small inputs) and only fail on edge cases. Defensive strategies:

// When iterating over a range [l, r]:
// Number of elements = r - l + 1  (inclusive on both sides)
// Number of elements = r - l      (inclusive left, exclusive right)

// Binary search — always verify your invariant:
int lo = 0, hi = n;  // [lo, hi) convention
while (lo < hi) {
    int mid = lo + (hi - lo) / 2;  // avoids overflow
    if (check(mid))
        hi = mid;     // answer is in [lo, mid]
    else
        lo = mid + 1; // answer is in [mid+1, hi)
}
// lo == hi == answer

Not Reading the Problem Statement Fully

This deserves its own section because it is that common. Typical symptoms:

Defense: after coding your solution, re-read the output format section before submitting. This takes 10 seconds and prevents the most embarrassing type of Wrong Answer.

Array Size and Memory Limits

Declaring arrays too small or too large are both problematic:

// Safe array sizing — add a small buffer
const int MAXN = 2e5 + 5;
int a[MAXN];

// If memory is tight, consider using vectors instead:
int n;
cin >> n;
vector<int> a(n);
for (int &x : a) cin >> x;

Forgetting to Reset Globals Between Test Cases

When a problem has multiple test cases and you use global data structures, failing to reset them between cases causes Wrong Answer on test case 2+. The solve() function pattern in the template above helps, but be careful with global arrays:

const int MAXN = 2e5 + 5;
bool visited[MAXN];

void solve() {
    int n;
    cin >> n;
    // ... build graph, run DFS ...

    // IMPORTANT: reset only the used portion
    for (int i = 0; i < n; ++i) visited[i] = false;
    // Do NOT use memset(visited, 0, sizeof(visited))
    // on every test case — it is O(MAXN) per call,
    // which is too slow if there are 10^5 test cases
}

Division-Specific Strategies

The optimal strategy varies significantly by division because the problem distribution and scoring system differ. Here is what to focus on at each level.

Div. 4 (Rating < 1400)

At this level, speed on easy problems is everything. Problems A through D are typically implementation, basic math, sorting, greedy, or simple data structure usage. Your goal: solve all 7–8 problems. The problems are designed to be solvable by beginners, so the differentiator is speed and bug-free implementation.

Focus areas: string manipulation, basic number theory (GCD, divisibility), sorting with custom comparators, prefix sums, two pointers. Know these techniques cold.

Div. 3 (Rating < 1600)

Div. 3 adds problems that require knowledge of standard algorithms: binary search on the answer, BFS/DFS on graphs, basic dynamic programming, and set/map operations. The last 2–3 problems are usually the dividing line between Specialist and Expert ratings.

Strategy: solve A–E quickly (under 60 minutes total), then invest remaining time in F and G. Problems F/G often involve DP, segment trees, or non-trivial graph algorithms. Even partial progress (e.g., solving F but not G) is a strong result.

Div. 2 (Rating < 1900)

This is where the transition from "knowing algorithms" to "applying them under pressure" becomes critical. Problem A is trivial, B is straightforward, C usually requires a clever observation or standard technique, D is a genuine algorithmic problem, and E/F are hard.

Expected topic distribution at this level:

Strategy: solve A+B in under 10 minutes, C in under 30 minutes, then push for D. If you can consistently solve D, you will reach Expert (1600+). If you can occasionally solve E, you will reach Candidate Master (1900+).

Div. 1 (Rating ≥ 1900)

At Div. 1, every problem is genuinely hard. Problem A is usually around Div. 2 D/E difficulty. The gap between solving 1 problem and solving 2 problems is massive in terms of rating. Speed on A is critical — most Div. 1 rating changes are determined by how fast you solve A and whether you solve B.

Strategy: commit to A immediately. If A looks like a topic you are weak in, read B before investing too much time — sometimes B is easier for your skillset. The expected value calculation from the "Problem Ordering" section is especially important at this level.

Post-Contest Analysis

The contest itself is only half the learning opportunity. What you do in the hours and days after a contest determines whether you extract lasting improvement or just experience temporary frustration.

The Upsolving Workflow

Upsolving means solving problems you failed to solve during the contest, after it ends. This is the single highest-leverage activity for improvement. Here is a structured approach:

  1. Immediately after the contest (within 30 minutes): re-attempt the first problem you failed to solve during the contest. You have the context fresh in your mind. Give yourself 30–45 minutes without looking at editorials.
  2. Read the editorial for problems you could not solve. Do not just read it — implement the solution from scratch. Understanding an editorial and being able to code it are very different skills.
  3. Identify the gap: was it a technique you did not know, or a technique you knew but failed to apply? The former requires study; the latter requires more practice with that technique.
  4. Add the problem to a tracking spreadsheet with columns: problem ID, topic, difficulty, whether you solved it, and what you learned. Over time, patterns emerge — you might discover you consistently fail on segment tree problems or constructive algorithms.

Virtual Contests

Codeforces allows you to participate in past contests as "virtual" participants, simulating real contest conditions. Virtual contests are invaluable because:

Recommendation: do 2–3 virtual contests per week in addition to participating in live contests. Choose contests from 6–12 months ago so you are unlikely to remember the problems but the difficulty is representative of current contests.

Rating Prediction and Mindset

Codeforces ratings follow an Elo-like system. Your rating change after a contest depends on your performance relative to your expected performance. Key facts:

The most important mindset shift: stop thinking about rating as a goal and start thinking about it as a trailing indicator of skill. If you focus on solving harder problems and eliminating mistakes, the rating follows. If you focus on the rating number itself, you make conservative choices (avoiding hard problems to "protect" your rating) that actually slow your growth.

Stress Testing Your Solutions

For post-contest analysis and upsolving, a stress-testing script is invaluable. It generates random test cases, runs your solution and a brute-force solution, and compares outputs:

// gen.cpp — random test generator
#include <bits/stdc++.h>
using namespace std;
int main(int argc, char* argv[]) {
    mt19937 rng(atoi(argv[1]));
    int n = rng() % 10 + 1;       // small n for brute force
    cout << n << "\n";
    for (int i = 0; i < n; ++i)
        cout << (int)(rng() % 100 + 1) << " \n"[i == n - 1];
    return 0;
}
# stress.sh — compare fast solution vs brute force
#!/bin/bash
g++ -O2 -o sol sol.cpp
g++ -O2 -o brute brute.cpp
g++ -O2 -o gen gen.cpp
for ((i = 1; ; ++i)); do
    ./gen $i > input.txt
    ./sol < input.txt > out1.txt
    ./brute < input.txt > out2.txt
    if ! diff -q out1.txt out2.txt > /dev/null; then
        echo "MISMATCH on test $i:"
        cat input.txt
        echo "--- sol output:"
        cat out1.txt
        echo "--- brute output:"
        cat out2.txt
        break
    fi
    echo "Test $i passed"
done

This approach finds corner cases that you would never think of manually. Run it after every contest on problems where you got Wrong Answer — the failing test case usually reveals the bug instantly. Build this into your standard workflow and you will eliminate an entire category of preventable mistakes.