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:
- Subtask-based scoring: Each problem is divided into 3–6 subtasks with increasing difficulty and point values. You earn points for each subtask you solve completely.
- Full feedback: You can submit as many times as you want and see your score for every subtask immediately. This is enormous — you know exactly where you stand at all times.
- No penalty for wrong submissions: Unlike ICPC or Codeforces, there is zero cost for submitting a wrong or slow solution. Submit early and submit often.
- Individual contest: No team dynamics to manage. Your score is entirely your own.
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:
- Free points: Subtask 1 is almost always solvable with brute force. Budget 10–15 minutes per problem for these.
- Medium effort: Subtasks 2–3 usually require a correct but not necessarily optimal algorithm. O(N²) or O(N³) solutions are fine.
- High effort: The final subtask often requires a non-trivial algorithmic insight or advanced data structure.
- Special structure subtasks: These can be deceptively easy or hard. "The tree is a line" might simplify a tree DP to a 1D problem. "The graph is bipartite" might unlock a matching-based approach.
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 |
Ordering Your Attack
A strong sequence for each contest day:
- Minutes 0–25: Read all three problems. Identify the easiest subtask 1 across all problems.
- Minutes 25–70: Implement and submit brute force for all three problems (subtask 1, possibly subtask 2).
- Minutes 70–180: Work on medium subtasks for the two problems where you have the clearest path.
- 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
- You can implement it quickly and correctly (under 20 minutes).
- The remaining subtasks require an algorithm you haven't fully figured out yet.
- You have two other problems that still need attention.
- It's early in the contest — bank points now, optimize later if time allows.
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:
- ~60% effort on your best problem: This is the one you have the best shot at a full or near-full score. Invest heavily here, but only after banking partial scores on the other two.
- ~30% effort on your second problem: Aim for subtasks 1–4. A 60–70 point score is excellent.
- ~10% effort on your hardest problem: At minimum, get the brute force subtasks. Even 10–20 points matters — medal cutoffs are tight.
But this is a guideline, not a rule. The actual allocation depends on problem difficulty and your strengths:
When to Switch Problems
Switch immediately if any of these are true:
- You've been stuck for 20+ minutes without making progress on the current subtask.
- You realize the next subtask requires an algorithm you don't know.
- You have a clear idea for a 15+ point subtask on another problem.
- You've just submitted and are waiting for results — use that time to read or think about another problem.
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:
- If you're close to a full solve: Commit to debugging and optimizing. A jump from 70 to 100 points is huge.
- If you're stuck everywhere: Go back to your brute-force solutions and see if there's a subtask you missed. Check for special-case subtasks you haven't attempted.
- If you have a working solution with one subtle bug: Add more test cases. Stress-test with a brute-force checker. Don't rewrite from scratch.
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
- Use
assert()liberally during development. Check array bounds, preconditions, and invariants. Remove them before the final submission if you're worried about speed (you usually won't need to). - Keep a stress-testing harness ready. Write a tiny brute-force checker and test against random inputs:
// 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
- Submit early. As soon as your brute-force compiles and passes the sample, submit it. There's no penalty, and you lock in points immediately.
- Submit incrementally. After each improvement, submit. Don't wait until everything is "done."
- Keep your best-scoring code saved. Before making major changes, copy your current solution to a backup. If the refactor breaks things, you can always revert.
- Check the scoreboard feedback. If your solution passes subtasks 1–3 but fails subtask 4, you know exactly what to debug.
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.
- Strategic impact: You must think about information theory. Each query should maximally reduce the search space. Binary search is your default tool.
- Implementation tip: Always flush output after each query
(
cout << endlorcout.flush()). Forgetting to flush is the #1 source of TLE on interactive problems. - Subtask strategy: The query limit often decreases across subtasks (e.g., 10000 queries for subtask 1, then 2N, then N + 10). Use a greedy or brute-force query strategy for early subtasks.
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:
- Run multiple algorithms and keep the best answer for each test case.
- Use randomized/heuristic approaches — simulated annealing, genetic algorithms, random restarts. These are rarely useful in normal competitive programming but shine for output-only tasks.
- Small test cases can be solved by hand or with brute force. Don't overthink subtask 1 of an output-only problem.
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:
- Think about encoding: How many bits of information do you need to transmit? What's the minimum encoding to reconstruct the answer?
- Subtask strategy: Early subtasks usually have generous communication limits. A naive encoding (just send everything) can work for partial credit.
- Don't panic: Communication tasks look scary but often have elegant solutions once you find the right encoding. Spend time thinking before coding.
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:
- Time tracking: Where did you spend too long? If you burned 90 minutes on a subtask that was worth 15 points, that's a signal to be more disciplined about switching on Day 2.
- Reading mistakes: Did you misread a problem statement? On Day 2, force yourself to re-read the problem after your first implementation plan — before writing code.
- Implementation speed: If your code was buggy, was it because you rushed or because the structure was messy? Adjust your coding style for Day 2.
- Subtask coverage: If you scored 0 on any problem because you never attempted it, that's a red flag. On Day 2, commit to getting brute force points on every problem within the first 60 minutes.
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:
- Do not study. You won't learn a new algorithm well enough overnight to use it under pressure. Focus on rest.
- Brief reflection only. Spend at most 20 minutes reviewing your Day 1 strategy (not the problems themselves). Write down 2–3 concrete behavioral adjustments.
- Physical activity helps. A walk or light exercise clears your mind better than sitting and worrying about Day 1 scores.
- Sleep. This is non-negotiable. A well-rested mind is worth more than any last-minute preparation. Aim for 8+ hours.
Day 2 Strategic Adjustments
Based on your Day 1 score and the medal cutoff estimates:
- If Day 1 went well (180+ points): Play conservatively on Day 2. Bank partial scores early. You don't need to take risks — consistent medium scores will secure your medal.
- If Day 1 was mediocre (100–170 points): Maintain your standard approach. Focus on efficient subtask collection. One strong problem solve (80+ points) combined with 40+ on the others can still push you over the medal line.
- If Day 1 was poor (< 100 points): You need to take calculated risks on Day 2. Identify the problem you're most likely to solve fully and invest heavily. Still get brute-force points on the others, but be willing to spend 3+ hours on one problem if you see a path to 80–100 points.
The Scoreboard Effect
At IOI, you can see an approximate live scoreboard. This is a double-edged sword:
- Useful: If very few people have high scores on a problem, it's probably genuinely hard. Don't waste time trying to fully solve it unless you have a clear idea.
- Dangerous: Seeing that someone else has 100 on a problem you're stuck on can cause panic. Ignore individual scores. Focus on your own plan.
- Tactical: If the scoreboard shows that most contestants have only subtask 1 on a problem, but you see a path to subtask 3, that's a high-value opportunity — it will differentiate you from the field.
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.