
#include <iostream>
#include <set>
#include <unordered_set>
#include <functional>
#include <chrono>
#include <cmath>
#include <random>
#include <ctime>
#include <ext/pb_ds/assoc_container.hpp>
using namespace __gnu_pbds;
using namespace std;

typedef gp_hash_table<
    int, null_type, hash<int>, equal_to<int>, direct_mask_range_hashing<int>, linear_probe_fn<>,
    hash_standard_resize_policy<hash_exponential_size_policy<>, hash_load_check_resize_trigger<true>, true>>
    gp;

typedef cc_hash_table<
    int, null_type, hash<int>, equal_to<int>, direct_mask_range_hashing<int>,
    hash_standard_resize_policy<hash_exponential_size_policy<>, hash_load_check_resize_trigger<true>, true>>
    cc;

int getTicks() {
    static auto startTime = std::chrono::high_resolution_clock::now();
    auto t = std::chrono::high_resolution_clock::now();
    return std::chrono::duration_cast<std::chrono::milliseconds>(t - startTime).count();
}
template <class T> struct hset {
    size_t sz, (*hfunc)(T);
    static constexpr uint8_t dead = 0, filled = 1, empty = 2;
    uint8_t *state;
    T *x;
    int buffer;
    hset(int s, size_t (*hf)(T) = [](T val) -> size_t {
        hash<T> hfunc;
        return hfunc(val);
    }) {
        hfunc = hf;
        sz = s;
        buffer = 2 * log2(sz) + 5; // a buffer lets us work faster because we don't have to check that pos<sz at the
                                   // cost of a minimal chance of runtime error + a little memory
        state = new uint8_t[sz + buffer];
        x = new T[sz + buffer];
        fill(state, state + sz + buffer, empty);
    }
    ~hset() {
        delete state;
        delete x;
    }
    void clear() { fill(state, state + sz + buffer, empty); }
    bool erase(const T &v) {
        int pos = hfunc(v) % sz;
        while (true) {
            if (state[pos] == empty)
                return false;
            if (state[pos] == filled && x[pos] == v) {
                state[pos] = dead;
                return true;
            }
            pos++;
        }
    }
    void erase_x(const T &v) // UNSAFE! crashes if element is not found
    {
        int pos = hfunc(v) % sz;
        while (true) {
            if (state[pos] == filled && x[pos] == v) {
                state[pos] = dead;
                return;
            }
            pos++;
        }
    }
    bool insert(const T &v) {
        int pos = hfunc(v) % sz;
        int first = -1;
        while (true) {
            if (state[pos] != filled) {
                if (state[pos] == empty) {
                    if (first == -1)
                        first = pos;
                    state[first] = filled;
                    x[first] = v;
                    return true;
                } else if (first == -1)
                    first = pos;
            } else if (x[pos] == v)
                return false;
            pos++;
        }
    }
    int count(const T &v) const {
        int pos = hfunc(v) % sz;
        while (true) {
            if (state[pos] == empty)
                return 0;
            if (state[pos] == filled && x[pos] == v)
                return 1;
            pos++;
        }
    }
    T *find(const T &v) const {
        int pos = hfunc(v) % sz;
        while (true) {
            if (state[pos] == empty)
                return NULL;
            else if (state[pos] == filled && x[pos] == v)
                return &x[pos];
            pos++;
        }
    }
    size_t size() { return sz; }
};
const int n = 2e5;
int a[n], b[n];
void testHSET() {
    hset<int> v(4 * n);
    for (int t = 0; t < 100; t++) {
        for (int i = 0; i < n; i++)
            v.insert(a[i]);
    }
    for (int i = 0; i < n; i++)
        v.erase(b[i]);
}
void testPBuset() {
    gp v;
    v.resize(n);
    v.set_loads({.25, .5});
    for (int t = 0; t < 100; t++) {
        for (int i = 0; i < n; i++)
            v.insert(a[i]);
    }
    for (int i = 0; i < n; i++)
        v.erase(b[i]);
}
void testPBuset2() {
    cc v;
    v.resize(n);
    v.set_loads({.25, .5});
    for (int t = 0; t < 100; t++) {
        for (int i = 0; i < n; i++)
            v.insert(a[i]);
    }
    for (int i = 0; i < n; i++)
        v.erase(b[i]);
}
void teststluset() {
    unordered_set<int> v;
    v.max_load_factor(0.5);
    v.reserve(n);
    for (int t = 0; t < 100; t++) {
        for (int i = 0; i < n; i++)
            v.insert(a[i]);
    }
    for (int i = 0; i < n; i++)
        v.erase(b[i]);
}
#define RANDUZ_MAX 0x0fffffff
int randuz() {
    static std::mt19937 gen(time(NULL));
    static std::uniform_int_distribution<int> d(0, RANDUZ_MAX);
    return d(gen);
}
void testgood() {
    unordered_set<int> s1;
    hset<int> s2(10000, [](int x) -> size_t { return x * x; });
    for (int i = 0; i < 0xfff; i++) {
        s1.insert(i);
        s2.insert(i);
    }
    for (int i = 0; i < 20000; i++) {
        int x = randuz() & 0xfff;
        switch (randuz() % 3) {
        case 0:
            s1.erase(x);
            s2.erase(x);
            break;
        case 1:
            s1.insert(x);
            s2.insert(x);
            break;
        case 2:
            if (s1.count(x) != s2.count(x))
                cout << "BAD\n";
            break;
        }
    }
}
int main() {
    testgood(); // test to make sure our hash set actually works
    for (int i = 0; i < n; i++)
        a[i] = randuz(), b[i] = randuz();
    ;
    int s = getTicks();
    testHSET();
    int t = getTicks();
    cout << "Custom hash set: " << t - s << "ms\n";
    for (int i = 0; i < n; i++)
        a[i] = randuz(), b[i] = randuz();
    s = getTicks();
    testPBuset();
    t = getTicks();
    cout << "gp_hash_table: " << t - s << "ms\n";
    for (int i = 0; i < n; i++)
        a[i] = randuz(), b[i] = randuz();
    s = getTicks();
    testPBuset2();
    t = getTicks();
    cout << "cc_hash_table: " << t - s << "ms\n";
    for (int i = 0; i < n; i++)
        a[i] = randuz(), b[i] = randuz();
    ;
    s = getTicks();
    teststluset();
    t = getTicks();
    cout << "unordered_set: " << t - s << "ms\n";
    return 0;
}
