← All Posts
Contest Strategy · Meta· Part 6 of 12

How to Read Editorials & Upsolve Effectively

Every competitive programmer has been there: the contest ends, you check the scoreboard, and realize you were tantalizingly close on problem D. You open the editorial, skim through it, think "oh, that makes sense," and move on. Two weeks later you encounter the same technique and draw a blank.

The gap between reading an editorial and actually learning from it is enormous. This post is about closing that gap. We'll build a systematic approach to editorial reading, upsolving, and long-term technique retention—the meta-skills that separate plateauing competitors from those who keep climbing.

The Art of Reading Editorials

An editorial is not a solution—it's a compressed record of someone else's thought process. The goal isn't to memorize the solution; it's to reconstruct the reasoning that leads to it. This requires a deliberate, layered reading approach.

Layer 1: The High-Level Idea

On your first pass, read only the first paragraph or the "key observation" section. Stop there. Ask yourself:

Don't look at the implementation yet. Give yourself 5–10 minutes to try solving the problem with just this hint. You'll be surprised how often the high-level idea alone is enough to unlock the solution.

Layer 2: The Technical Details

If the hint wasn't sufficient, continue reading. Focus on:

Layer 3: The Implementation

Only after you understand the approach should you read the code. When you do, focus on the non-obvious parts: tricky indexing, edge case handling, the specific way a data structure is used. These are the details that trip you up during implementation.

The "close the tab" test. After reading an editorial, close it. Can you explain the solution to an imaginary teammate without looking back? If not, you haven't truly understood it.

When to Read an Editorial

Timing matters. Reading an editorial too early robs you of the struggle that builds problem-solving muscle. Reading too late wastes time you could spend learning new techniques.

The 30-60 Minute Rule

For problems at or slightly above your level, spend at least 30–60 minutes of genuine effort before opening the editorial. "Genuine effort" means:

Diminishing Returns

There's a curve to productive struggle. The first 30 minutes are almost always valuable—you're building mental models, trying approaches, and deepening your understanding of the problem space. But after 60–90 minutes with no new ideas, the returns diminish sharply:

Time SpentTypical BenefitAction
0–30 minHigh—building understanding, testing ideasKeep working
30–60 minModerate—exploring deeper, refining approachesKeep trying, consider reading a hint
60–90 minLow—often stuck in a local minimumRead the high-level idea (Layer 1)
90+ minVery low—frustration outweighs learningRead the full editorial

Exception: Known Technique Gaps

If you realize early on that the problem requires a technique you've never seen (e.g., Centroid Decomposition and you don't know what that is), there's no shame in reading the editorial quickly. The goal is to learn the technique, not to reinvent it from scratch. Your struggle budget is better spent on problems where you have the tools but need to find the right combination.

The Upsolving Workflow

Upsolving—solving problems after the contest—is where most learning happens. But passive upsolving (read editorial, copy code, submit) is nearly worthless. Here is a structured workflow that actually builds skill:

Step 1: Attempt During Contest

During the contest, jot down your approach, what you tried, and where you got stuck. Even failed ideas are valuable context for later.

Step 2: Post-Contest Review (Same Day)

Within a few hours of the contest ending, while your memory is fresh:

Step 3: Implement Without Looking

This is the crucial step most people skip. Close the editorial and implement the solution from your understanding alone. You may need to re-read sections, but the goal is to minimize peeking. Here's a real example—suppose the editorial for a segment tree problem reveals you need lazy propagation with range assignment:

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

struct SegTree {
    int n;
    vector<long long> tree, lazy;
    vector<bool> has_lazy;

    SegTree(int n) : n(n), tree(4 * n, 0),
                     lazy(4 * n, 0), has_lazy(4 * n, false) {}

    void push_down(int node, int lo, int hi) {
        if (!has_lazy[node]) return;
        int mid = (lo + hi) / 2;
        apply(2 * node, lo, mid, lazy[node]);
        apply(2 * node + 1, mid + 1, hi, lazy[node]);
        has_lazy[node] = false;
    }

    void apply(int node, int lo, int hi, long long val) {
        tree[node] = val * (hi - lo + 1);
        lazy[node] = val;
        has_lazy[node] = true;
    }

    void update(int node, int lo, int hi, int l, int r, long long val) {
        if (r < lo || hi < l) return;
        if (l <= lo && hi <= r) {
            apply(node, lo, hi, val);
            return;
        }
        push_down(node, lo, hi);
        int mid = (lo + hi) / 2;
        update(2 * node, lo, mid, l, r, val);
        update(2 * node + 1, mid + 1, hi, l, r, val);
        tree[node] = tree[2 * node] + tree[2 * node + 1];
    }

    long long query(int node, int lo, int hi, int l, int r) {
        if (r < lo || hi < l) return 0;
        if (l <= lo && hi <= r) return tree[node];
        push_down(node, lo, hi);
        int mid = (lo + hi) / 2;
        return query(2 * node, lo, mid, l, r)
             + query(2 * node + 1, mid + 1, hi, l, r);
    }

    void update(int l, int r, long long val) { update(1, 0, n - 1, l, r, val); }
    long long query(int l, int r) { return query(1, 0, n - 1, l, r); }
};

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

    int n, q;
    cin >> n >> q;

    SegTree seg(n);
    for (int i = 0; i < n; i++) {
        long long x; cin >> x;
        seg.update(i, i, x);
    }

    while (q--) {
        int type; cin >> type;
        if (type == 1) {
            int l, r; long long v;
            cin >> l >> r >> v;
            seg.update(l, r, v);
        } else {
            int l, r;
            cin >> l >> r;
            cout << seg.query(l, r) << "\n";
        }
    }
}

The test: can you write this from understanding alone, without copy-pasting? If you get stuck on push_down, that tells you exactly what to reinforce.

Step 4: Submit and Verify

Submit your implementation to the online judge. Getting AC on your own code, based on your own understanding, is the real signal that you've learned something. If you get WA or TLE, debug it yourself first before re-reading the editorial.

Step 5: Revisit After 1–2 Weeks

The forgetting curve is real. Revisit the problem after a week or two and try to solve it again from scratch. If you can, the technique is genuinely in your toolkit. If you can't, you know what needs more work.

Extracting Techniques from Editorials

The highest-value outcome of reading an editorial isn't solving that specific problem—it's adding a reusable technique to your mental toolkit. Here's how to extract and retain techniques systematically.

Identify the Key Insight

Every editorial solution hinges on one or two critical observations. Train yourself to isolate them. Common categories:

Build a Technique Vocabulary

After extracting the key insight, record it in a structured way. Over time, you build a personal technique index. Each entry should have:

FieldExample
Technique nameAlien's trick (WQS binary search / lambda optimization)
When to useDP with a constraint on the number of "groups" or "segments" used, where the unconstrained DP is concave
How it worksBinary search on a penalty λ added per group; the optimal λ forces exactly k groups
ComplexityO(n log C) where C is the value range, instead of O(n·k)
Reference problemsCF 868F, IOI 2014 Holiday, APIO 2014 Sequence

Pattern Recognition Shortcuts

As your vocabulary grows, you start recognizing trigger patterns in problem statements:

These shortcuts don't replace thinking, but they dramatically narrow the search space for the right approach.

Learning from Others' Code

Editorials give you the idea. But reading accepted submissions from strong contestants teaches you how to implement ideas cleanly and efficiently. This is an underrated learning channel.

What to Look For

A Practical Approach

After solving a problem (or upsolving it), look at 3–5 accepted submissions from top-rated users. Compare:

Quality over quantity. Don't read 50 submissions. Read 3–5 carefully. One well-understood alternative approach is worth more than a dozen skimmed solutions.

Studying Coding Style

Pay attention to naming, spacing, and structure. Competitive programming code doesn't need to be beautiful, but it does need to be debuggable under pressure. If you find yourself consistently making bugs, studying how clean coders structure their implementations can reveal organizational patterns that prevent errors.

Building an Upsolving Queue

After every contest, you'll have 2–5 problems you didn't solve. Without a system, these pile up and you never get to them. An upsolving queue solves this by giving you a prioritized, trackable list of problems to work through.

Prioritization Strategy

Not all unsolved problems are equally valuable. Prioritize by:

  1. Just above your level: Problems you almost solved (had the right direction but missed the key step) teach the most.
  2. New technique exposure: Problems requiring techniques you haven't seen are high-value—even if they're hard.
  3. Reinforcement: If you've seen a technique once but aren't fluent, prioritize a second problem using it.
  4. Skip (for now): Problems that are 500+ rating points above you or require very niche techniques. Return to these later.

A Simple Tracking System

You don't need fancy tools. A simple struct and a text file work fine. Here's a lightweight C++ sketch for maintaining an upsolving log:

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

struct UpsolveProblem {
    string problem_id;    // e.g., "CF1900E"
    string contest;       // e.g., "CF Round 912"
    int difficulty;       // estimated rating
    string technique;     // key technique needed
    string status;        // "pending", "read_editorial", "implemented", "ac", "revisited"
    string notes;         // what you learned
};

// Priority: problems closest to your rating come first,
// then by technique novelty
bool compare(const UpsolveProblem& a, const UpsolveProblem& b) {
    int my_rating = 1600;
    int gap_a = abs(a.difficulty - my_rating);
    int gap_b = abs(b.difficulty - my_rating);
    if (gap_a != gap_b) return gap_a < gap_b;
    return a.status < b.status;  // earlier status = higher priority
}

void print_queue(vector<UpsolveProblem>& queue) {
    sort(queue.begin(), queue.end(), compare);
    cout << "=== Upsolving Queue ===\n";
    for (int i = 0; i < (int)queue.size(); i++) {
        auto& p = queue[i];
        cout << i + 1 << ". [" << p.status << "] "
             << p.problem_id << " (" << p.difficulty << ") - "
             << p.technique << "\n";
        if (!p.notes.empty())
            cout << "   Notes: " << p.notes << "\n";
    }
}

int main() {
    vector<UpsolveProblem> queue = {
        {"CF1900E", "CF Round 912", 1900, "segment tree + lazy",
         "pending", ""},
        {"CF1850G", "CF Round 887", 1700, "binary search on answer",
         "read_editorial", "Key: monotonicity of the check function"},
        {"CF1920F", "CF Round 920", 2100, "centroid decomposition",
         "pending", ""},
        {"CF1870D", "CF Round 898", 1800, "greedy + sorting",
         "ac", "Sort by deadline, greedily assign earliest slot"},
        {"CF1840E", "CF Round 880", 1600, "dp on subsets",
         "implemented", "Bitmask DP, iterate over submasks"},
    };

    print_queue(queue);

    // After upsolving, update status and add notes:
    // queue[0].status = "ac";
    // queue[0].notes = "Lazy prop pattern: always push before recurse";
}

The key fields are technique (what you're learning), status (where you are in the workflow), and notes (what you actually learned). The notes field is the most valuable part—it's your future self's cheat sheet.

Weekly Review Rhythm

Set a regular cadence: after each contest, add 2–3 problems to your queue. During the week, upsolve 3–5 problems from the queue. Once a month, review your notes to refresh techniques that have faded. This steady rhythm compounds over months into a massive technique repertoire.

Common Pitfalls in Upsolving

Even with a good workflow, there are traps that undermine your learning. Here are the most common ones and how to avoid them.

Pitfall 1: Copy-Paste Without Understanding

The most common and most damaging mistake. You read the editorial, copy the code (or type it while looking at the solution), get AC, and feel productive. But you've learned almost nothing—you've just practiced typing.

Fix: Always implement from understanding, not from the editorial's code. If you can't implement without looking, you don't understand it yet. Go back and re-read the approach.

Pitfall 2: Never Revisiting Problems

You upsolve a problem, get AC, and check it off forever. Two months later, you can't remember how you solved it. The Ebbinghaus forgetting curve is brutal: without review, you'll lose 60–70% of what you learned within a week.

Fix: Use spaced repetition. Revisit problems at increasing intervals: 1 day, 1 week, 1 month. You don't need to re-code the entire solution—just mentally reconstruct the approach and verify you could write it.

Pitfall 3: Over-Reliance on Editorials

Some competitors develop a reflex: if they can't solve it in 10 minutes, they reach for the editorial. This short-circuits the productive struggle phase and prevents you from developing problem-solving intuition.

Fix: Enforce a minimum struggle time. Set a timer. During that time, the editorial is off-limits. Use the 30–60 minute guideline from the When to Read section.

Pitfall 4: Upsolving Only Easy Problems

It's psychologically rewarding to upsolve problems just barely above your solve boundary—you get lots of AC's with modest effort. But you're not stretching. Real growth comes from the problems that are 200–400 rating points above your current level.

Fix: In your upsolving queue, maintain a ratio. For every 3 problems near your level, upsolve 1–2 that are significantly harder. These harder problems won't yield quick AC's, but they'll expose you to new techniques and thinking patterns.

Pitfall 5: Not Taking Notes

You solve the problem, feel good, and move on. A month later, someone asks "How do you approach segment tree problems with range operations?" and you have no structured answer.

Fix: After every upsolve, write at least one sentence: the key insight, the technique, or the implementation trick you learned. These notes compound into an invaluable personal reference.

Pitfall 6: Treating All Editorials Equally

Some editorials are excellent—they explain the thought process, the failed approaches, and why the solution works. Others just dump code with a one-line explanation. If you're learning from a bad editorial, supplement with blog posts, tutorial videos, or forum discussions about the problem.

Fix: If the official editorial is unclear, search for community explanations on Codeforces blogs, competitive programming YouTube channels, or discussion threads. Multiple perspectives often clarify what a single editorial obscures.

The compound effect. If you upsolve just 3 problems per week with proper technique extraction and notes, that's 150+ techniques per year. Over 2–3 years, you'll have internalized more patterns than most competitors encounter in their entire career. Consistency beats intensity every time.