Template I developed while improving from Expert to Master

Правка en5, от dcclyde, 2022-08-20 11:05:37

Template I developed while improving Expert -> Master

Use this template and you'll gain 350 rating right away! OK maybe not, but it has a bunch of cool features I built up while climbing from 1800 to 2150 in February-May of this year, so I'm sharing it here. I'll organize this post as follows:

  1. Demos of specific cool tricks that the template allows
  2. Why a good template is important, aka what a good template can (and can't) do for you

I hope the code is readable enough so people can steal just a few of my tricks if that's what seems useful to them. Also very interested in feedback/suggestions/requests about how to make the template more useful :)

Before I get into details, here again are links to the current template version on GitHub and the version as of posting this blog.

Also, here's an embedded copy in case someone prefers to see it without leaving CodeForces.

Specific cool tricks in the template

Debug printouts

Here's an output screenshot:

...and here's the code I used :

void solve() {
    ll a = 14; double b = -3e-2; char c = '@'; string s = "Test String";
    vector<ll> q = {1, 6, 4, 3, 5, 3, 1};
    dbg(a, b, c, "", s, q);
    dbgc("show this on left", make_pair(a, c));  // dbgc = "debug with 1st arg as comment"
    dbgcP("diff colors", a, b);  // uppercase letters change the color; all terminal colors are available
    dbgc("expressions work too", 2*b, gcd(a, a+4));
    el;  // empty line

    // complex/nested data structures work too.
    map<pair<ll,ll>, V<string>> dat = {
        {{3, 5}, {"abc", "def"}},
        {{4, -1}, {"apple", "peach", "banana"}},
        {{-5, 0}, {"tourist", "jiangly", "um_nik", "slime", "ksun48"}}
    };
    dbgY(dat);
    // that may be pretty messy to read all on one line though, so we can use:
    dbg(pdh(dat));
    el;

    // show how "regular output" lines look visibly different from dbg() lines.
    cout << a << ' ' << b << ' ' << c << '\n';

    // what if we have a big object and we want to "get the idea" of its contents
    // without printing the whole thing?
    vector<pll> vbig; FOR(k, 0, 100) {vbig.emplace_back(k, -k*k);}
    dbgR(pdh(vbig, 10));  // short for "print_details_helper"

    el;
    // Advanced: pdhf lets me specify a function, so f(x) will be printed for each x in the structure.
    dbg(pdhf(vbig, [&](auto x){return x.second;}, 8));  // pdhf = "print_details_helper_func"

    dbgcBold("done!");
    return;
}

Summary of key dbg() features:

  • dbg(a, b, c) prints line number, exact text you passed in, and the values of all those variables/expressions/objects. Optional dbgc version uses the first argument as a marker or comment, which is placed to the left. All printouts are indented to keep them visually separate from the normal outputs to cout.
  • Different color options so you can easily see which printouts came from which dbg() call. Default color is cyan if you just use dbg(); specify other colors with dbgP (purple), dbgY (yellow), and so on. All the ANSI terminal colors are provided except black; see the template code for the full list.
  • Handles pairs, tuples, and STL data structures including arbitrary nesting.
  • All dbg() and el stuff is removed at preprocessor stage if we didn't pass flag -DDCCLYDE_LOCAL when compiling. That means I don't need to bother commenting out these lines before submitting to online judges. (Regular cerr printouts won't cause WA at most judges, but they could easily cause TLE.)
  • pdh ("print details helper") functionality prints one entry per line; useful if we're printing a complex object since printing on one line can be hard to visually process. Template includes some extensions, demod above.

Real-life example:

The demo above may make it seem like the different colors are just visually noisy instead of useful. Here's an example of how I'd use the printouts in "real life" on a recent div2 C.

Submission link

Just the solve() function

Here's what I see when I run the sample test cases locally:

Personally, I normally use Red = print out inputs as soon as I read them to confirm I loaded them correctly; Yellow = higher-level info, maybe printing out results of a big intermediate step; Cyan (default) = less important stuff, or printouts coming from inside a work loop; Purple = printouts from inside a function call or nested loop. This organization helps me visually see right away where the different printouts came from. Of course I could always use the printed line numbers too, but my brain is a little faster at processing the color differences.

"Unhackable" and faster unordered_map and unordered_set replacements

unordered_map and unordered_set are notoriously hackable and so generally should not be used on CodeForces; see classic blog post. Also, they are usually around 5x slower than the alternative gp_hash_table, see another blog. My template defines replacements umap and uset which fix both of those problems, basically by implementing the recommendations from those linked posts. Bonus: my versions also happily hash arbitrary pairs, tuples, or iterable STL objects. I make it easy to toggle which implementation is used by umap and uset. Caveat: pairs and tuples worked well, but hashing more complex stuff like std::vectors often leads to TLE.

I/O shortcuts

These aren't a huge deal, but they do save me from typing the same annoying lines of code in every problem.

// INPUT
lls(R, C);  // declare and read from stdin two long longs called R and C. Can also use ints, chars, strings for other datatypes.
ll1(a, b);  // same as lls except decrement the variables by 1 after reading; useful when problems give 1-indexed inputs but I want to work with 0-indexed
V<int> q;
rv(R, q);  // resize q to size R and then fill all entries from stdin
rv1(R, q);  // same as rv except decrement by 1 after reading

// OUTPUT
ps(a, b, R);  // same as cout << a << ' ' << b << ' ' << R << '\n';
ps1(a, b);  // same as cout << a+1 << ' ' << b+1 << '\n'; notice we ADD 1 when outputting.
pv(q);  // output space-separated all on one line, with newline at the end
pv1(q);  // same as pv but add 1 to each output

Precompiled headers

When compiling on judge server, I use a few includes:

#include <tgmath.h>
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>

When compiling locally, I instead only use #include "/home/dcclyde/puzzles/code/templates/superheader.h". That's because superheader.h is a local file that has all those same includes, but I've set up a precompiled header which makes my compilations several seconds faster, like from 6s to 1.5s on my laptop. Basically this hack keeps the compile time manageable even though my template has all those #includes and #defines.

I'm not linking a specific guide for how to build a precompiled header because the correct setup will depend on your OS and machine configuration. However, I was able to make it work pretty quickly once I searched Google for precompiled header C++ gcc ubuntu, so I hope you'll have good luck here too.

What a good template can/can't do for you

I've seen threads where people argue that a good coder shouldn't rely on a long template. IMO it's totally fine to think that, and I know many strong coders use a short template or no template. By contrast, I think it's very very hard to reach high ratings without a strong library of data structures / algorithms you can quickly paste in or modify.

The main things I think a template helps with are

  1. Reduce repetitive typing: save 1-5 minutes on each problem by using small prewritten functions/macros for I/O, binary searches, etc. This can also make it a little easier to code correctly: less code typed for each problem means less places to look for the bug. On CF this is pretty important, since getting problems A and B in 1-3 mins instead of 5-10 minutes helps my rating a lot.
  2. Make debugging faster and easier: even when the intermediate steps are producing complicated data structures, the dbg() functionality lets me track down unexpected/buggy behaviour very fast. Before I had this template I used to code new debug printouts on every problem, and coding the printouts would distract me so my brain couldn't focus fully on actually finding my error.

Overall, the template makes it way easier and faster to go from "having the right idea" to "getting AC". I always feel frustrated if I have the correct plan but don't end up getting credit for the problem, so making the implementation smoother helps me a lot with feeling motivated and happy while training, especially when learning new ideas/algorithms that I'm not very comfortable with.

Unfortunately, your template can't help you have the right solution ideas in the first place. For that you'll have to refer to the million blogs that already exist with more general advice about how to train. My TLDR advice is a) solve practice contests, and try hard to upsolve 1-2 additional problems after each contest; b) read editorials but only after thinking about the problem for at least a few hours, and implement some of those solutions instead of just reading and moving on; c) building up an algorithm library is not very important when trying to reach CM but very useful when targeting Master and above, and using others' libraries is OK but you have to practice with the code and tune it to your liking so you'll be able to modify it quickly during competition without adding bugs.

Acknowledgements

The dbg() functionality is a heavily modified version of ideas stolen from tourist and tantam75, see blog.

When creating my template, I started from Benq's TemplateLong.cpp (GitHub). My version still has lots of minor convenience features from his template.

Of course I've copied ideas from a ton of other sources too, mostly other CF blogs. Thanks everyone!

Questions, suggestions, advice?

I'm very interested to hear to advice about how to accomplish the same things in a better way, or feature requests that would make the template features nicer to use, or even just random questions about why I did things a certain way. Some of the "weird" decisions are made on purpose to get around non-obvious issues that I didn't discuss in this blog, and others may be just because I'm clumsy with some of the tools I'm using here, so even asking "silly" questions can be useful — either I'll explain something you didn't see, or I'll learn something from your idea.

This is my first blog post after using CodeForces off and on for many years, so I'm also interested to hear advice about how to make a more useful blog next time.

Теги template, c++ template, debugging, preprocessor

История

 
 
 
 
Правки
 
 
  Rev. Язык Кто Когда Δ Комментарий
en8 Английский dcclyde 2022-08-23 02:08:23 31386 Edited the "links to different template versions" section.
en7 Английский dcclyde 2022-08-22 14:17:49 0 (published)
en6 Английский dcclyde 2022-08-22 14:15:40 6044 1.0
en5 Английский dcclyde 2022-08-20 11:05:37 503 precompiled header text
en4 Английский dcclyde 2022-08-20 10:52:55 274 minor
en3 Английский dcclyde 2022-08-20 10:08:26 802 minor
en2 Английский dcclyde 2022-08-20 09:43:25 14645 first "complete" version
en1 Английский dcclyde 2022-08-20 07:30:01 36647 wip (saved to drafts)