Note
Some of the tricks mentioned in this document are advanced and might not be suited for begineer level. If you are facing trouble understanding this, you might want to come back to this later after a few weeks.
A small terminal-based workflow for compiling/running Codeforces-style solutions quickly, with local-only debug code and a couple of micro-benchmarks.
build() {
clang++ -std=c++20 "$1.cc" -o "$1" -g -DLOCAL
}- Builds
X.ccinto./X -DLOCALenables#ifdef LOCALblocks-gadds debug symbols (useful forgdb/lldb)
bar() {
build "$1"
cat /dev/stdin | "./$1"
}
# Usage
echo "
<program input copied from Codeforces>" | bar <solution_basename>Notes:
- If your file is
A.cc, run:bar A - This form reads stdin and executes the compiled binary.
int main() {
#ifdef LOCAL
// This runs locally when compiled with -DLOCAL.
// On Codeforces (no LOCAL), it compiles out.
std::cout << "Testing code here\n";
#endif
}import os
# Set CODEFORCES_LOCAL_TESTING=true locally to enable this block.
if os.getenv("CODEFORCES_LOCAL_TESTING") == "true":
print("testing code here...")Primitive types are pre-defined types like int, long, char, float, double.
When using map, these are compared using the std::less<> which uses the < operator which is much faster than hashing and comparing.
Hashing is computationally expensive. std::map<> often provides better search time complexity. Remember that O(K) does not involve the time of hashing. Hashing is not cache friendly as well
Often a times solutions get TLEd due to using hashmaps (std::unordered_map<>) instead of maps.
Same reasoning as for above
Some data structures have specific insertion/deletion operations which perform better in certain cases when you provide some additional information. For example when inserting elements to a sorted C++ std::set<>.
#include <iostream>
#include <set>
int main() {
std::set<int> s;
for (int i = 0; i != 2'000'000; i++) s.insert(i);
std::cout << "Inserted " << s.size() << " elements successfully\n";
}- Typical complexity: O(log n) per insert → O(n log n) total.
#include <iostream>
#include <set>
int main() {
std::set<int> s;
auto it = s.begin();
for (int i = 0; i != 2'000'000; i++) it = s.insert(it, i);
std::cout << "Inserted " << s.size() << " elements successfully\n";
}insert(pos, value)takes a hint iterator.- With increasing values and reusing the returned iterator, the hint is usually correct, reducing search work.
- If the hint is bad, it falls back toward O(log n) behavior.
inline functions are functions where the functions are not actually compiled but placed alongside your code. It is a compiler optimization technique.
// In C/C++ replace every of your function from:
int myFunc(...) { /* logic */ }
// to
inline int myFunc(...) { /* logic */ }inline functions are free-optimizations which can help you pass your code through testing in edge cases
new / malloc / frequent container growth are often expensive due to allocator overhead and cache misses.
Habits that help:
- Preallocate when possible (
reserve,resize, static arrays) - Reuse buffers across test cases
- Avoid per-element heap allocation in hot loops (e.g., millions of
std::setnodes) - Try to move allocations of data structures as much out of loops as possible
Examples:
#include <iostream>
int main() {
int t;
static int a[200'001]; // fixed allocation once
for (std::cin >> t; t; t--) {
int n;
std::cin >> n;
}
}#include <iostream>
int main() {
int t;
for (std::cin >> t; t; t--) {
int n;
std::cin >> n;
int a[n]; // stack VLA: non-standard in C++ (GCC extension), can blow stack
}
}#include <string>
#include <vector>
int main() {
// Preallocate your strings/vectors to size that you know it won't exceed.
// Often a times much faster than having to let the data structure grow by itself
// When the data structure grows itself, memory is copied from old space to new space which takes O(n) time
std::string s;
s.reserve(1'000'000);
std::vector<int> v;
v.reserve(1'000'000);
}On Codeforces, only stdout is judged. Output to stderr is ignored by the checker, so it’s safe for debug logs.
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
cerr << "n = " << n << "\n"; // debug only (stderr)
cout << (n * 2) << "\n"; // judged output (stdout)
}Also works:
fprintf(stderr, "debug: i=%d\n", i);import sys
n = int(input())
print(f"debug: n={n}", file=sys.stderr) # ignored by CF judging
print(n * 2) # judged outputTip: keep debug logs on stderr, keep required answers on stdout.
Macros are text substitution. If a macro uses its argument more than once, passing an expression with side effects is dangerous.
Bad macro:
#define all(x) for (auto y = x.begin(); y != x.end(); y++)Problematic use:
int i = 0;
all(v[i++]) {
std::cout << *y << "\n";
}After expansion:
int i = 0;
for (auto y = v[i++].begin(); y != v[i++].end(); y++) {
std::cout << *y << "\n";
}v[i++] appears twice (begin() and end()), so i++ runs multiple times → mismatched iterators and likely undefined behavior.
Safer patterns:
- Don’t pass side-effect expressions into macros.
- Use range-for, or store the expression once:
auto& row = v[i++];
for (auto it = row.begin(); it != row.end(); ++it) {
std::cout << *it << "\n";
}Rule of thumb: if a macro uses its parameter more than once, side-effect arguments are a bug.