ZhangShan's blog

By ZhangShan, history, 7 weeks ago, In English

TL;DR: I built Scriba — a free, open-source (MIT) renderer that turns a LaTeX editorial (math, prose, tables, code, and step-by-step algorithm animations) into one self-contained, sanitized HTML file. Animations are driven by real code, not hand-drawn frames. To see it on real problems right now: 50 CSES problems where every editorial is explained with an animation — open any of them and press play. Or write your own in the playground, no signup.

Hello, Codeforces!

If you've ever written an editorial — for a school contest, a gym, or just a detailed solution comment — you know the drill. The prose and the math are the easy part. Then you hit "and now the DP table fills up like this", and you're hand-drawing cells in Excalidraw, screenshotting each state into step1.png, step2.png, … Then you find an off-by-one in your own explanation, and all nine screenshots are garbage. Re-draw, re-shoot, re-upload.

I did this one too many times while building the editorial corpus for my judge, and got tired of it. So I built something.

The friction with hand-made diagrams

  • Screenshots go stale the moment the algorithm logic changes — fix a bug, redraw every frame.
  • A GIF can't be paused mid-step, and readers can't step backwards.
  • The diagram lives in a different tool than the statement, so they drift apart silently.
  • KaTeX/MathJax solve the math beautifully, but an editorial is math + prose
  • tables + code + "watch what happens at step 4".
  • Sharing source with a co-setter means "here's the .tex, and also this Excalidraw link, and these PNGs".

What Scriba does

  • Full statements, not just math: math, prose, tables, and code highlighting in one document, rendered server-side (the math inside goes through KaTeX — credit where due).
  • 21 animation primitives: array, DP table, matrix/heatmap, graph, tree, stack, queue, deque, linked list, hash map, number line, code panel, variable watch… most editorial diagrams map 1-1 to a shape you already think in.
  • Frames driven by real code: a sandboxed Starlark block (\compute / \foreach) runs your actual loop and fills frames from its output — fix the logic once, every frame updates.
  • Self-contained & sanitized: math, styles, and the player script all inline in one HTML file — drop it into any page, no toolchain. Untrusted setters' statements can't script-inject your site.
  • Free, MIT — live playground with shareable snippets, or pip install scriba-tex.

(Honest scope: Scriba renders a LaTeX subset — body content plus its own animation syntax — not full LaTeX.)

What the source looks like

A minimal 3-step animation:

\begin{animation}[id="hello", label="Hello Scriba"]
\shape{a}{Array}{size=5, data=[3,1,4,1,5], label="my array"}

\step
\narrate{A simple array with 5 elements.}

\step
\recolor{a.cell[2]}{state=current}
\narrate{Highlight the middle element.}

\step
\recolor{a.cell[2]}{state=done}
\narrate{Mark it as done.}
\end{animation}

That renders as an inline player with play / pause / step controls.

Scriba player rendering the 3-step array animation

The interesting part is when frames come from real logic. This is an actual example from the guide — the matrix-chain DP table, computed by the real nested loops inside a \compute block, then poured into the table:

\begin{animation}[id="matrix-chain", label="Matrix-chain DP table"]
\shape{dp}{DPTable}{rows=5, cols=5, label="dp[i][j]"}
\compute{
  n = 5
  p = [30, 35, 15, 5, 10, 20]
  dp_vals = [[0 for _ in range(n)] for _ in range(n)]
  for length in range(2, n + 1):
      for i in range(n - length + 1):
          j = i + length - 1
          dp_vals[i][j] = 10**9
          for k in range(i, j):
              cost = dp_vals[i][k] + dp_vals[k+1][j] + p[i] * p[k+1] * p[j+1]
              if cost < dp_vals[i][j]:
                  dp_vals[i][j] = cost
}

\step
\foreach{i}{0..4}
  \foreach{j}{0..4}
    \apply{dp.cell[${i}][${j}]}{value=${dp_vals[i][j]}}
  \endforeach
\endforeach
\narrate{Minimum matrix-chain multiplication cost table, built with nested for loops.}
\end{animation}

If I later realize the cost formula was wrong, I fix the loop — not nine screenshots. And unlike VisuAlgo's fixed canonical examples, this runs your problem's data and your transition.

Don't take my word for it — 770 real problems use this

Every editorial on my judge (app.judge.zone) is rendered with Scriba, in 8 languages (including RTL Arabic). If you only click one link in this post, make it the first one:

  • 50 CSES problems — intro to advanced, where every editorial explains the algorithm with a step-by-step animation: watch the DP table fill or the tree get re-rooted, instead of parsing three paragraphs of index notation. Open any editorial and press play — that's the whole pitch.
  • 720 USACO problems — 65 contests, 2011–2026. The volume test: one pipeline, no per-page hand-tuning.

(And even if you never write editorials, the playground doubles as a free scratchpad for stepping through your own DP while upsolving.)

Try it

Open scriba.judge.zone — no signup. The LaTeX guide and animation guide cover every command with runnable examples. To render locally or self-host: pip install scriba-tex (v0.39.0), source on GitHub (MIT).

Feedback / bug reports / feature requests

  1. Next primitive: Trie, DSU forest, or Fenwick/BIT — which one would you actually use first?
  2. Which LaTeX command do you miss most inside statements?

Bug reports and PRs welcome on GitHub, or come argue about animation timing in the Discord: discord.gg/pg52FspEns.

Thanks

Scriba stands on KaTeX — all math inside statements is rendered through it server-side, and it's an absurdly good piece of software. Thanks also to every editorialist on CF whose hand-drawn DP tables and graph doodles over the years quietly defined the list of 21 primitives — this tool is basically those whiteboard drawings, made reproducible.

Happy animating.

Full text and comments »

  • Vote: I like it
  • +24
  • Vote: I do not like it

By ZhangShan, history, 5 months ago, In English

TL;DR: I built stress.ojcloud.net — a free online stress tester for competitive programmers. Paste your main, brute, and gen into three tabs, hit Run, and it streams per-test verdicts and stops at the first counterexample with Input / Main Output / Brute Output side-by-side. No signup, no install, 8 languages (C++ 03/11/14/17/20, C, Python/PyPy, Java, Kotlin, Go, Rust).

Hello, Codeforces!

If you've been doing CP for more than a week, you already know the routine. You submit, you get Wrong answer on pretest 2, the test is hidden, and your solution is 300 lines long. You open a terminal, mkdir stress, write the same while true; do ./gen > in; ./a.out < in > out1; ./brute < in > out2; diff out1 out2 || break; done you've written a hundred times, realize you forgot to seed the generator, rewrite it, forget to recompile brute, finally see a diff, and now you're squinting at 2000 random numbers trying to minimize the counterexample by hand.

We all do it. tourist does it, Um_nik does it, I do it. Stress testing is probably the single most valuable debugging skill in CP, and yet every single one of us has hand-rolled the same bash script a hundred times.

The friction with the bash approach

  • Boilerplate every single time: compile three files, seed loop, diff, break.
  • No UI. You can't see which test is running or how far along you are.
  • Switching languages (Python brute, C++ main) means rewriting the runner.
  • No history. Close the terminal, lose everything.
  • Sharing a counterexample with a teammate means pasting 3 files into Discord.
  • Forget to -O2 the brute and it takes 40 seconds per test. Forget to seed the RNG and every test is identical.

I got tired of this, so I built something.

What stress.ojcloud.net does

  • 8 languages: C++ 03/11/14/17/20, C, Python 3, PyPy 3, Java, Kotlin, Go, Rust. Switch from a dropdown; templates auto-load.
  • 5 built-in generator snippets: Array, Tree, Graph, String, Grid — one click drops working code into the gen tab. Each snippet uses a random(lo, hi) helper and a seed from argv[1], so runs are reproducible.
  • Real-time verdicts: the right panel streams Test 1 → Test 2 → … live. Stops the moment it finds a mismatch — no waiting for the remaining 900 tests.
  • Counterexample card: shows Input, Main Output, Brute Output side-by-side with one-click copy on each.
  • Configurable tests & timeout: default 200 tests / 2s per test, tune to taste.
  • Browser-resumable session: close the tab mid-run, reopen it, your code and last result are still there.
  • 7-language UI: English / Tiếng Việt / Русский / 简体中文 / 日本語 / 한국어 / Português.
  • Free. No signup. No account. No email. Just open the page and paste.

Demo: finding a real bug in ~30 seconds

Let's do the most classic bug in CP — Kadane's on max subarray sum, with best = 0 instead of LLONG_MIN. Invisible on positive inputs, fatal on all-negative ones.

main (buggy O(n) Kadane):

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n; cin >> n;
    vector<long long> a(n);
    for (auto& x : a) cin >> x;
    long long best = 0, cur = 0;          // BUG: should be LLONG_MIN
    for (long long x : a) {
        cur = max(x, cur + x);
        best = max(best, cur);
    }
    cout << best << "\n";
}

brute (O(n²), definitely correct):

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n; cin >> n;
    vector<long long> a(n);
    for (auto& x : a) cin >> x;
    long long best = LLONG_MIN;
    for (int i = 0; i < n; i++) {
        long long s = 0;
        for (int j = i; j < n; j++) { s += a[j]; best = max(best, s); }
    }
    cout << best << "\n";
}

gen: click the Array snippet, tweak the range to random(1, 8) and random(-10, 10) so negatives actually appear. Hit Run.

Within a few tests the stream stops on:

Input          Main Output    Brute Output
2              0              -3
-3 -5

There's the bug. All-negative array, buggy Kadane returns 0, correct answer is -3. Fix the init to LLONG_MIN, re-run, green "All 200 tests passed". Total time: under a minute.

Try it

stress.ojcloud.net — open the page, paste 3 files, hit Run. That's it. No signup flow to click through.

If you prefer a walkthrough, here's a 30-second video covering the exact Kadane example above: youtu.be/XI1HNegwFuI.

Feedback / bug reports / feature requests

I'd love to hear what breaks, what's missing, and what languages/snippets you'd want next. Join the Discord: discord.gg/pg52FspEns — share counterexamples, report bugs, argue about generators.

Thanks

Huge thanks to MikeMirzayanov — the checker design for OJCloud's main judge was shaped heavily by testlib.h, and the same philosophy (strict, readable, reproducible) carried over into this stress runner. And thanks to the countless CF blogs over the years that taught everyone the gen / main / brute / diff pattern in the first place — this tool is just that pattern with a UI bolted on.

Happy stressing.

Full text and comments »

  • Vote: I like it
  • +64
  • Vote: I do not like it

By ZhangShan, history, 6 months ago, In English

TL;DR: I couldn't register on usaco.org (Gmail verification bug), so I built OJCloud — all 484 USACO problems (2011–2024, Bronze → Platinum) on a modern judge with partial scoring, 16 languages, real-time per-test-case verdicts, progress tracking, and both ICPC/IOI contest modes. Everything uses standard I/O. Register with just a handle + password — no email, no OAuth, no waiting.

Full text and comments »

  • Vote: I like it
  • +247
  • Vote: I do not like it

By ZhangShan, history, 2 years ago, In English

Hello, Codeforces!

We are happy to invite you to CodeQuest which contains many problems on many topics for you to exercise and improve your algorithms knowledge.

Our goal is to make a pathway for everyone to reach their aim with only a Codeforces account !

It currently contains these topics (with 100+ problems):

  • Prefix Sums

  • Custom Comparators

  • Coordinate Compression

  • Two Pointers

  • Sorted Sets

  • Greedy Algorithms

  • Binary Search

And we have reformatted these problems to make it more readable (I think) from the original sources !

We will add more problems and more topics in the future, hope that you guys enjoy it.

We currently just summarize problems from USACO Guide, we think that we will summarize more problems from more sources. The reason why we summarize problems from USACO Guide is because we know that not many people have the account on USACO and many other platform and those problems are really good for exercising.

Join Discord Server ! (for discussing about CP problems and we will make some video and living tutorial for these problems).

Update: Our Discord server has reached 120+ people, i'm really thank you.

Full text and comments »

  • Vote: I like it
  • +63
  • Vote: I do not like it