#include <array>
#include <bit>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <utility>
constexpr int MAX_N = 2'000'000;
constexpr int ROOT = 1414;
// Păstrăm doar imparele:
// index 0 -> 1
// index 1 -> 3
// index 2 -> 5
// ...
constexpr std::size_t ODD_COUNT = (MAX_N + 1) / 2;
// Număr de impare per bloc.
constexpr std::size_t ODDS_PER_BLOCK = 1u << 16; // 65536
// 65536 biți / 64 = 1024 uint64_t
constexpr std::size_t WORDS_PER_BLOCK =
ODDS_PER_BLOCK / 64;
constexpr std::size_t BLOCK_COUNT =
(ODD_COUNT + ODDS_PER_BLOCK - 1) / ODDS_PER_BLOCK;
// ----------------------------------------------------
// Primele până la sqrt(MAX_N).
// E suficient de mic încât să fie calculat direct
// la compile-time.
// ----------------------------------------------------
consteval auto makeSmallSieve()
{
std::array<bool, ROOT + 1> prime{};
for (int i = 2; i <= ROOT; ++i) {
prime[i] = true;
}
for (int p = 2; p * p <= ROOT; ++p) {
if (!prime[p]) {
continue;
}
for (int x = p * p; x <= ROOT; x += p) {
prime[x] = false;
}
}
return prime;
}
inline constexpr auto SMALL_PRIME = makeSmallSieve();
// ----------------------------------------------------
struct Block {
std::array<std::uint64_t, WORDS_PER_BLOCK> bits{};
};
// ----------------------------------------------------
// Fiecare bloc este calculat într-o evaluare
// consteval separată.
// ----------------------------------------------------
template <std::size_t B>
consteval Block makeBlock()
{
Block block{};
for (auto& word : block.bits) {
word = ~0ULL;
}
constexpr std::size_t firstIndex =
B * ODDS_PER_BLOCK;
constexpr std::size_t endIndex =
(firstIndex + ODDS_PER_BLOCK < ODD_COUNT)
? firstIndex + ODDS_PER_BLOCK
: ODD_COUNT;
constexpr std::size_t validBits =
endIndex - firstIndex;
constexpr std::uint64_t low =
2ULL * firstIndex + 1;
constexpr std::uint64_t high =
2ULL * (endIndex - 1) + 1;
// Primul bloc conține 1.
if constexpr (B == 0) {
block.bits[0] &= ~1ULL;
}
// Marcăm compusele folosind doar primele impare
// <= sqrt(MAX_N).
for (int p = 3; p <= ROOT; p += 2) {
if (!SMALL_PRIME[p]) {
continue;
}
std::uint64_t first =
static_cast<std::uint64_t>(p) * p;
// Primul multiplu al lui p din bloc.
if (first < low) {
first =
((low + p - 1) / p) * p;
}
// Stocăm doar numere impare.
if ((first & 1ULL) == 0) {
first += p;
}
for (std::uint64_t x = first;
x <= high;
x += 2ULL * p) {
const std::size_t index =
static_cast<std::size_t>(
(x - low) >> 1
);
block.bits[index >> 6] &=
~(1ULL << (index & 63));
}
}
// Ultimul bloc poate fi incomplet.
if constexpr (validBits < ODDS_PER_BLOCK) {
constexpr std::size_t fullWords =
validBits / 64;
constexpr unsigned remaining =
validBits % 64;
if constexpr (remaining != 0) {
block.bits[fullWords] &=
(1ULL << remaining) - 1;
for (std::size_t i = fullWords + 1;
i < WORDS_PER_BLOCK;
++i) {
block.bits[i] = 0;
}
} else {
for (std::size_t i = fullWords;
i < WORDS_PER_BLOCK;
++i) {
block.bits[i] = 0;
}
}
}
return block;
}
// ----------------------------------------------------
// IMPORTANT:
// acestea sunt obiecte constexpr SEPARATE.
// Astfel nu avem o singură evaluare gigantică.
// ----------------------------------------------------
template <std::size_t B>
struct SieveBlock {
inline static constexpr Block value = makeBlock<B>();
};
// Facem doar un array mic de pointeri către blocuri.
template <std::size_t... Is>
constexpr auto makeBlocks(std::index_sequence<Is...>)
{
return std::array<const Block*, sizeof...(Is)>{
&SieveBlock<Is>::value...
};
}
inline constexpr auto BLOCKS =
makeBlocks(std::make_index_sequence<BLOCK_COUNT>{});
// ----------------------------------------------------
int countPrimes(int n)
{
if (n < 2) {
return 0;
}
// 2 este singurul prim par.
int answer = 1;
const std::size_t oddCount =
static_cast<std::size_t>((n + 1) / 2);
const std::size_t fullBlocks =
oddCount / ODDS_PER_BLOCK;
const std::size_t partial =
oddCount % ODDS_PER_BLOCK;
// Blocuri complete.
for (std::size_t b = 0; b < fullBlocks; ++b) {
const auto& block = *BLOCKS[b];
for (std::uint64_t word : block.bits) {
answer += std::popcount(word);
}
}
// Ultimul bloc parțial.
if (partial != 0) {
const auto& block = *BLOCKS[fullBlocks];
const std::size_t fullWords =
partial / 64;
const unsigned remaining =
partial % 64;
for (std::size_t i = 0; i < fullWords; ++i) {
answer += std::popcount(block.bits[i]);
}
if (remaining != 0) {
const std::uint64_t mask =
(1ULL << remaining) - 1;
answer += std::popcount(
block.bits[fullWords] & mask
);
}
}
return answer;
}
// ----------------------------------------------------
int main()
{
std::freopen("ciur.in", "r", stdin);
std::freopen("ciur.out", "w", stdout);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::cout << countPrimes(n) << '\n';
return 0;
}