Coding without the SOI-header

Written by Daniel Rutschmann.

During the programming workshops, you were using the SOI header with #include<soi>. The SOI header simplifies simplifies input and output and helps with a few other things. This article explains how you achieve similar things in vanilla c++ (i.e. without using the SOI header).

Including the STL

With the SOI header, you’re already including the c++ standard template library, so you can use things like std::vector or std::sort. If you don’t have access to the SOI header, you can instead do the following.

// include everything from stl
#include <bits/stdc++.h>
// avoid typing std:: for stl stuff
using namespace std;

signed main(){
    // speed up input / output
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    // code here
}

The first two lines in main() speed up input and output. This can make a big different in runtime if you’re reading \(10^{4}\) or more integers.

If you’re on a mac, then you’re probably using clang, so including <bits/stdc++.h> won’t work. In that case, you need to look in the c++ reference to see which headers you need to include. Including the following ones should be sufficient for most use cases.

// include individual headers
#include <algorithm>
#include <array>
#include <bitset>
#include <cassert>
#include <cmath>
#include <complex>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <deque>
#include <forward_list>
#include <fstream>
#include <functional>
#include <initializer_list>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <memory>
#include <ostream>
#include <queue>
#include <random>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>
#include <unordered_set>
#include <unordered_map>
#include <utility>
#include <valarray>
#include <vector>

Input-Output

With the SOI header, you read numbers with read_int, strings with read_string, etc. In vanilla c++, you use cin to read input. The syntax is cin >> variable_1 >> variable_2 >> variable_3;. You can read an arbitrary number of variables this way, but you need to declare the variables beforehand.

signed main(){
    int n; // declare int
    cin >> n; // read int x
    string s, t;
    cin >> s >> t; // read two strings
}

The above program is equivalent to

signed main(){
    int n = read_int();
    string s = read_string();
    string t = read_string();
}

To read a vector, use a for loop.

signed main(){
    int n;
    cin >> n;
    vector<int> v(n), w(n);
    // with normal for loop
    for(int i = 0; i < n; ++i){
        cin >> v[i];
    }
    // with range-based for loop
    for(int &e : w){
        cin >> e;
    }
    // with auto and range-based for loop
    for(auto &e : w){
        cin >> e;
    }
}

To print output in vanilla c++, use cout << variable_1 << " " << variable_2 << "\n";. Note that you need to print spaces " " and newlines "\n" yourself.

signed main(){
    int n = 5, m = 7;
    cout << n << " " << m << "\n";
    cout << "Hello SOI!\n";
}

The above program is equivalent to

signed main(){
    int n = 5, m = 7;
    print(n, m);
    print("Hello SOI");
}

For vectors, use a for loop once again.

signed main(){
    int n = 5;
    vector<int> a(n);
    // with normal for loop
    for(int i = 0; i < n; ++i){
        cout << a[i] << " ";
    }
    cout << "\n";
    // with range-based for loop
    for(int const&e : a){
        cout << e << " ";
    }
    cout << "\n";
    // with auto and range-based for loop
    for(auto const&e : a){
        cout << e << " ";
    }
    cout << "\n";

Note that all the above loops print a space after the last element of the vector. This usually does not matter, as the grader ignores trailing whitespace.

Things you shouldn’t use

There are a few other ways of doing input/output. This sections covers some of the ones you should avoid.

  1. C-style IO: printf, scanf, puts, gets, … These functions are slower than cin and cout with optimizations, clunky to use and bug prone due to their format strings. There is no reason to use them in the SOI.
  2. Slow newlines: endl prints a newline and then flushes the output. This is very slow and even slower on the grader. Use '\n' or "\n" to print a newline. (The one exception to this are interactive tasks, but then we’ll usually explicitly mention it in the description of those tasks.)

Overflows

In vanilla c++, int can only hold \(32\)-bit integers, so it can only store values up to \(2^{31}- 1\) which is slightly above \(2 \cdot 10^{9}\). If try to store larger numbers, you cause an overflow, which is undefined behaviour. Usually the number will wrap around to negative numbers. (But you could also get an infinite loop for instance.) The SOI header increases this limit to \(2^{63}- 1\) which is around \(9 \cdot 10^{18}\). This has a performance cost however.

If you want to use numbers up to \(2^{63}- 1\), use long long or int64_t instead of int. The most common case for this is when you want to multiply to ints.

signed main(){
    int a = 1000000000;
    int b = 1000000000;
    int c = 2 * a; // ok, c is 2 * 10^9
    int d = a * b // overflow, d usually is -1486618624
    long long e = a * b; // overflow again
    // to avoid the overflow, convert to long long first
    long long f = a * (long long) b;
    int64_t g = a * (int64_t) b; // same as f
}

You can replace int by \(64\)-bit integers for your whole program with the following line at the start of your program. But remember that there is a performance cost, so you might get TLE due to this.

// include stl first
#include <bits/stdc++.h>

// replace int by int64_t
#define int int64_t

// use signed to avoid a compiler error
signed main(){
    // code here
}

Debugging

With the soi header, you can debug with the fancy dbg() macro. While there is no perfect replacement for it in vanilla c++, you can use cerr to print debugging information that will be ignored by the grader. Be warned though: Printing into cerr slows down your program, so you might get TLE if you print too much. The syntax is similar to the one of cout.

signed main(){
    cerr << "Start of main function\n";
    int n = 5;
    for(int i=0;i<n;++i){
        cerr << "Iteration " << i << " of " << n << "\n";
    }
    if(n == -1){
        cerr << "This code should not be reached\n";
    }
    // use a loop to print vectors
    vector<int> v(5);
    cerr << "v: ";
    for(auto const&e:v){
        cerr << e << " ";
    }
    cerr << "\n";
}

Output written to cerr is flushed immediately, so you’ll see your output even if your program crashes right afterwards.