#include <bits/stdc++.h>
using namespace std;
const int N = 1e8;
bool comp[N];
vector<int> primes;

void normal_sieve() {
    memset(comp, 0, sizeof comp);

    primes.clear();
    bool done = false;
    for (int i = 2; i < N; i++) {
        if (!comp[i]) {
            primes.push_back(i);

            if (i * i > N) {
                done = true;
            }

            if (!done)
                for (int j = i * i; j < N; j += i)
                    comp[j] = 1;
        }
    }
}

void normal_optimized_sieve() {
    memset(comp, 0, (sizeof comp) / 2);
    primes.clear();

    primes.push_back(2);
    int n = N / 2;
    int sr = round(sqrt(N));
    for (int i = 1; i < n; i++) {
        if (!comp[i]) {
            int p = 2 * i + 1;
            primes.push_back(p);

            if (p > sr)
                continue;

            for (int j = (p + 1) * i; j < n; j += p)
                comp[j] = 1;
        }
    }
}

void linear_sieve() {
    memset(comp, 0, sizeof comp);
    primes.clear();
    for (int i = 2; i < N; i++) {
        if (!comp[i])
            primes.push_back(i);
        for (int j = 0, si = primes.size(); j < si && i * primes[j] < N; j++) {
            comp[primes[j] * i] = 1;
            if (i % primes[j] == 0)
                break;
        }
    }
}

void linear_sieve_optimized() {
    memset(comp, 0, (sizeof comp) / 2);
    primes.clear();
    primes.push_back(2);
    for (int i = 1, p = 3; p < N; i++, p+=2) {
        if (!comp[i])
            primes.push_back(p);
        for (int j = 1, si = primes.size(); j < si && p * primes[j] < N; j++) {
            comp[(primes[j] * p) >> 1] = 1;
            if (p % primes[j] == 0)
                break;
        }
    }
}

void segmented_sieve() {
    int n = N;
    const int S = 50000;

    primes.clear();
    int nsqrt = round(sqrt(n));
    vector<char> is_prime(nsqrt + 1, true);

    ::primes.push_back(2);
    vector<char> block(S);

    vector<int> primes, start_indices;
    for (int i = 3; i <= nsqrt; i += 2) {
        if (is_prime[i]) {
            primes.push_back(i);
            start_indices.push_back(i * i);
            for (int j = i * i; j <= nsqrt; j += 2 * i)
                is_prime[j] = false;
        }
    }

    for (int k = 0; k * S <= n; k++) {
        fill(block.begin(), block.end(), true);
        int start = k * S;
        for (auto i = 0u; i < primes.size(); i++) {
            int p = primes[i];
            int idx = start_indices[i];
            for (; idx < S; idx += 2 * p)
                block[idx] = false;
            start_indices[i] = idx - S;
        }
        if (k == 0)
            block[1] = false;
        for (int i = 1; i < S && start + i <= n; i += 2) {
            if (block[i])
                ::primes.push_back(start + i);
        }
    };
}

void segmented_sieve_optimized() {
    int n = N;
    const int S = 30000;

    primes.clear();
    int nsqrt = round(sqrt(n));

    vector<char> is_prime(nsqrt + 1, true);
    vector<int> primes, start_indices;
    for (int i = 3; i <= nsqrt; i += 2) {
        if (is_prime[i]) {
            primes.push_back(i);
            start_indices.push_back((i * i - 1) / 2);
            for (int j = i * i; j <= nsqrt; j += 2 * i)
                is_prime[j] = false;
        }
    }

    ::primes.push_back(2);
    vector<char> block(S);
    int high = (n - 1) / 2;
    for (int low = 0; low <= high; low += S) {
        fill(block.begin(), block.end(), true);
        for (auto i = 0u; i < primes.size(); i++) {
            int p = primes[i];
            int idx = start_indices[i];
            for (; idx < S; idx += p)
                block[idx] = false;
            start_indices[i] = idx - S;
        }
        if (low == 0)
            block[0] = false;
        for (int i = 0; i < S && low + i <= high; i++) {
            if (block[i])
                ::primes.push_back((low + i) * 2 + 1);
        }
    };
}

void aitken() {
    int n = N;
    std::vector<bool> m(n + 1, false);

    int root = std::sqrt(n) + 1;
    for (int i = 1; i <= root; i++) {
        for (int j = 1; j <= root; j++) {
            int a = 4 * i * i + j * j;
            int b = 3 * i * i + j * j;
            int c = 3 * i * i - j * j;

            if (a <= n && (a % 12 == 1 || a % 12 == 5))
                m[a].flip();
            if (b <= n && b % 12 == 7)
                m[b].flip();
            if (i > j && c <= n && c % 12 == 11)
                m[c].flip();
        }
    }

    for (int i = 5; i * i <= n; i++) {
        if (m[i]) {
            for (int j = 1; j * i * i <= n; j++)
                m[j * i * i] = false;
        }
    }

    primes.clear();
    primes.push_back(2);
    primes.push_back(3);
    for (int i = 5; i < n; i++) {
        if (m[i])
            primes.push_back(i);
    }
}

template <typename T>
void time_compare(string name, T func, vector<int> comparison) {
    primes.clear();
    auto t0 = clock();
    (*func)();
    auto t1 = clock();
    if (!comparison.empty())
        assert(comparison == primes);
    cout << setw(20) << name << ": " << (1.0 * (t1 - t0)) / CLOCKS_PER_SEC << " seconds"
         << endl;
}

int main() {
    ::primes.reserve(6'000'00);

    time_compare("normal", normal_sieve, {});
    auto comparison = primes;
    time_compare("normal_optimized", normal_optimized_sieve, comparison);
    time_compare("linear", linear_sieve, comparison);
    time_compare("linear_optimized", linear_sieve_optimized, comparison);
    time_compare("segmented", segmented_sieve, comparison);
    time_compare("segmented_optimized", segmented_sieve_optimized, comparison);
    time_compare("aitken", aitken, comparison);
}
