← All Posts
Contest Strategy· Part 4 of 12

IOI Strategy — Partial Scoring, Subtasks & Time Allocation

The International Olympiad in Informatics is fundamentally different from every other major competitive programming contest. Unlike ICPC or Codeforces where a problem is either solved or not, IOI uses partial scoring with subtasks. This changes everything: your strategy, your implementation style, and how you allocate every one of those 300 minutes per day.

This post covers the strategic meta-game of IOI. We won't discuss specific algorithms — instead, we focus on how to maximize your total score across all six problems over two contest days.

IOI Format Overview

The IOI contest spans two days, each consisting of a 5-hour session with 3 problems. Each problem is worth 100 points, giving a maximum of 300 points per day and 600 total. The key features that distinguish IOI from other contests:

A typical IOI problem's subtask structure looks like this:

Subtask Constraints Points Typical Approach
1 N ≤ 10 5–10 Brute force / Enumeration
2 N ≤ 300 15–20 O(N³) or O(N² log N)
3 N ≤ 5000 20–25 O(N²)
4 Special structure (e.g. tree is a line) 15–20 Exploit special structure
5 N ≤ 200 000 (full) 30–35 O(N log N) or better

The implication is clear: you don't need the optimal algorithm to score well. A contestant who gets subtasks 1–3 on all three problems (roughly 40–55 points each) scores 120–165 points — often enough for a bronze medal.

Subtask Strategy

Not all subtasks are created equal. Understanding which subtasks to target — and in which order — is the single most important strategic skill at IOI.

Read All Problems First

Spend the first 20–30 minutes reading all three problems and their subtask breakdowns. You need a global view before committing to any single problem. For each problem, mentally classify the subtasks:

The Subtask ROI Calculation

Think in terms of points per minute. A rough heuristic:

Action Time Points ROI
Subtask 1 brute force 10–15 min 5–10 ~0.5–1.0 pts/min
Subtask 2–3 with O(N²) 30–60 min 30–45 ~0.5–1.5 pts/min
Special structure subtask 20–45 min 15–20 ~0.3–1.0 pts/min
Full solution (last subtask) 60–180 min 30–35 ~0.2–0.6 pts/min
Key insight: Getting the first 50–60 points on a problem is usually 3–5× more efficient (points per minute) than getting the last 30–40 points. Breadth-first across problems almost always beats depth-first on a single problem.

Ordering Your Attack

A strong sequence for each contest day:

  1. Minutes 0–25: Read all three problems. Identify the easiest subtask 1 across all problems.
  2. Minutes 25–70: Implement and submit brute force for all three problems (subtask 1, possibly subtask 2).
  3. Minutes 70–180: Work on medium subtasks for the two problems where you have the clearest path.
  4. Minutes 180–300: Attempt full solutions or hard subtasks on your strongest problem. Keep checking if a special structure subtask on another problem might be quicker.

Partial Scoring Optimization

One of the hardest mental shifts for IOI newcomers: an O(N²) solution that scores 40 points is not a failure. It's 40 points you bank while deciding whether to invest time optimizing to O(N log N) for the remaining 60. Here's when each approach makes sense.

When O(N²) Is the Right Call

Example: Brute-Force Partial Score

Suppose the problem asks for the maximum sum subarray across Q queries, each modifying one element. The full solution requires a segment tree with lazy propagation, but subtasks 1–3 (N, Q ≤ 5000) can be solved with brute force:

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

// O(N) Kadane's for max subarray sum
long long kadane(const vector<int>& a) {
    long long best = LLONG_MIN, cur = 0;
    for (int x : a) {
        cur = max((long long)x, cur + x);
        best = max(best, cur);
    }
    return best;
}

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

    int n, q;
    cin >> n >> q;
    vector<int> a(n);
    for (int& x : a) cin >> x;

    while (q--) {
        int pos, val;
        cin >> pos >> val;
        a[pos] = val;
        // O(N) per query — total O(NQ), good for N,Q <= 5000
        cout << kadane(a) << "\n";
    }
}

This solution runs in O(NQ) time. For N, Q ≤ 5000, that's 25 million operations — easily within the time limit. You score subtasks 1–3 (roughly 40–50 points) in about 10 minutes of implementation time.

Example: Targeted Special-Case Solution

Many IOI problems have a subtask like "the graph is a tree" or "all weights are equal." Writing a separate solution for that case can be quick and high-value:

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

int n;
vector<int> adj[200005];
int depth[200005], parent[200005];

void dfs(int u, int p, int d) {
    depth[u] = d;
    parent[u] = p;
    for (int v : adj[u]) {
        if (v != p) dfs(v, u, d + 1);
    }
}

// Subtask: graph is a tree — answer LCA queries with brute-force climbing
int lca(int u, int v) {
    while (depth[u] > depth[v]) u = parent[u];
    while (depth[v] > depth[u]) v = parent[v];
    while (u != v) { u = parent[u]; v = parent[v]; }
    return u;
}

int dist(int u, int v) {
    int w = lca(u, v);
    return depth[u] + depth[v] - 2 * depth[w];
}

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

    int q;
    cin >> n >> q;
    for (int i = 0; i < n - 1; i++) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    dfs(1, 0, 0);

    while (q--) {
        int u, v;
        cin >> u >> v;
        cout << dist(u, v) << "\n";
    }
}

This O(N) per query LCA is fine for the "tree subtask" where Q ≤ 1000. It scores 15–20 points with minimal implementation effort.

Knowing When to Upgrade

After banking partial scores on all three problems, revisit your strongest problem and ask: "Can I upgrade from O(N²) to O(N log N) in under 45 minutes?" If yes, the extra 30–40 points are worthwhile. If you're unsure about the algorithm or it requires heavy implementation, the time is usually better spent improving scores on other problems.

Time Allocation Across Problems

You have 300 minutes per day. How you distribute that time across three problems is often the difference between silver and gold.

The 60-30-10 Approach

A practical starting framework for how to think about the three problems:

But this is a guideline, not a rule. The actual allocation depends on problem difficulty and your strengths:

Dynamic reallocation rule: Every 60 minutes, pause for 2 minutes and reassess. Ask: "Where can I gain the most points in the next 60 minutes?" If the answer has changed, switch problems. Don't let sunk cost keep you on a problem where you're stuck.

When to Switch Problems

Switch immediately if any of these are true:

The Final Hour

The last 60 minutes deserve special planning. By this point, you should have partial scores on all three problems. Your strategy depends on your situation:

Implementation Strategies for IOI

At IOI, clean code isn't just good practice — it's a competitive advantage. You'll be modifying, extending, and debugging your solutions multiple times as you target successive subtasks. Spaghetti code costs you points.

The Modular Solution Template

Structure your solution so that upgrading from an O(N²) approach to O(N log N) requires changing one function, not rewriting everything:

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

// ── Input ──────────────────────────────────────
int n, q;
vector<int> a;

void read_input() {
    cin >> n >> q;
    a.resize(n);
    for (int& x : a) cin >> x;
}

// ── Core Logic (swap this for different subtasks) ──
namespace solver {
    // Brute-force version: O(N) per query
    void init() {
        // no preprocessing needed for brute force
    }

    void update(int pos, int val) {
        a[pos] = val;
    }

    long long query(int l, int r) {
        long long sum = 0;
        for (int i = l; i <= r; i++) sum += a[i];
        return sum;
    }
}

// When ready, replace the namespace above with:
// namespace solver {
//     // Segment tree version: O(log N) per query
//     long long tree[800005];
//     void build(int v, int l, int r) { ... }
//     void update(int v, int l, int r, int pos, int val) { ... }
//     long long query(int v, int l, int r, int ql, int qr) { ... }
//
//     void init() { build(1, 0, n - 1); }
//     void update(int pos, int val) { ... }
//     long long query(int l, int r) { ... }
// }

// ── Output ─────────────────────────────────────
void solve() {
    solver::init();
    while (q--) {
        int type;
        cin >> type;
        if (type == 1) {
            int pos, val;
            cin >> pos >> val;
            solver::update(pos, val);
        } else {
            int l, r;
            cin >> l >> r;
            cout << solver::query(l, r) << "\n";
        }
    }
}

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

This pattern lets you submit the brute-force version for easy subtasks, then swap in the optimized namespace when you're ready — without touching the I/O or control flow.

Defensive Programming Habits

// stress_test.cpp — run locally between submissions
#include <bits/stdc++.h>
using namespace std;

mt19937 rng(42);

string gen_input(int n) {
    // Generate random test case as a string
    string s = to_string(n) + "\n";
    for (int i = 0; i < n; i++) {
        if (i) s += " ";
        s += to_string(rng() % 1000000 - 500000);
    }
    return s + "\n";
}

int main() {
    for (int test = 0; test < 10000; test++) {
        int n = rng() % 20 + 1;
        string input = gen_input(n);

        // Run both solutions, compare outputs
        // (pipe through your brute-force and optimized solution)
        // If outputs differ, print the input and stop
    }
    cout << "All tests passed!\n";
}

Submission Strategy

Common IOI Problem Types

IOI problems often use non-standard formats that change how you approach them. Knowing what to expect eliminates surprise and lets you plan your implementation immediately.

Standard Batch Problems

The majority of IOI problems are standard: read input, compute answer, write output. These play to familiar competitive programming skills. Strategy here is straightforward — focus on algorithmic correctness and speed.

Interactive Problems

Your program communicates with a judge through queries. You ask questions and the judge responds. Common constraints: limited number of queries, adaptive adversaries.

Output-Only Problems

You're given the actual test inputs and must upload output files. No time limit — you can use any computation you want, including manual solutions. These appear occasionally and require a different mindset:

Communication Tasks

Two separate programs must coordinate to solve a problem, but they can only communicate through a limited channel (e.g., encoding information into a sequence of bits). These are rare but high-impact:

How Problem Type Changes Strategy

Type First Priority Time Risk
Standard batch Brute force → optimize Low — predictable implementation
Interactive Get I/O protocol right Medium — flush bugs waste time
Output-only Solve small cases immediately Low — no time limit pressure
Communication Understand the channel model High — easy to waste time on wrong encoding

Day 1 vs Day 2 Adjustments

IOI is a two-day contest, and the overnight break is a strategic asset. How you use it can meaningfully affect your Day 2 performance.

Learning from Day 1

After Day 1, critically review your performance:

Mental Recovery

The night between days is not for cramming algorithms. Your brain needs rest to perform at peak the next day. A sound strategy for the evening:

Day 2 Strategic Adjustments

Based on your Day 1 score and the medal cutoff estimates:

Remember: IOI medals are determined by total score across both days. A terrible Day 1 followed by an excellent Day 2 can still earn a medal. Never give up after Day 1 — the distribution of scores means there's almost always a realistic path to recovery.

The Scoreboard Effect

At IOI, you can see an approximate live scoreboard. This is a double-edged sword:

IOI is not about solving every problem perfectly. It's about maximizing your total score given finite time and energy. The contestants who medal are not always the ones who solve the hardest subtasks — they're the ones who collect points most efficiently across all six problems.