#pragma GCC optimize("Ofast") #pragma GCC optimize("unroll-loops") #include using namespace std; using lint = long long; using pint = pair; using plint = pair; struct fast_ios { fast_ios(){ cin.tie(nullptr), ios::sync_with_stdio(false), cout << fixed << setprecision(20); }; } fast_ios_; #define ALL(x) (x).begin(), (x).end() #define FOR(i, begin, end) for(int i=(begin),i##_end_=(end);i=i##_begin_;i--) #define REP(i, n) FOR(i,0,n) #define IREP(i, n) IFOR(i,0,n) template void ndarray(vector& vec, const V& val, int len) { vec.assign(len, val); } template void ndarray(vector& vec, const V& val, int len, Args... args) { vec.resize(len), for_each(begin(vec), end(vec), [&](T& v) { ndarray(v, val, args...); }); } template bool chmax(T &m, const T q) { if (m < q) {m = q; return true;} else return false; } template bool chmin(T &m, const T q) { if (m > q) {m = q; return true;} else return false; } int floor_lg(long long x) { return x <= 0 ? -1 : 63 - __builtin_clzll(x); } template pair operator+(const pair &l, const pair &r) { return make_pair(l.first + r.first, l.second + r.second); } template pair operator-(const pair &l, const pair &r) { return make_pair(l.first - r.first, l.second - r.second); } template vector sort_unique(vector vec) { sort(vec.begin(), vec.end()), vec.erase(unique(vec.begin(), vec.end()), vec.end()); return vec; } template istream &operator>>(istream &is, vector &vec) { for (auto &v : vec) is >> v; return is; } template ostream &operator<<(ostream &os, const vector &vec) { os << '['; for (auto v : vec) os << v << ','; os << ']'; return os; } template ostream &operator<<(ostream &os, const array &arr) { os << '['; for (auto v : arr) os << v << ','; os << ']'; return os; } #if __cplusplus >= 201703L template istream &operator>>(istream &is, tuple &tpl) { std::apply([&is](auto &&... args) { ((is >> args), ...);}, tpl); return is; } template ostream &operator<<(ostream &os, const tuple &tpl) { std::apply([&os](auto &&... args) { ((os << args << ','), ...);}, tpl); return os; } #endif template ostream &operator<<(ostream &os, const deque &vec) { os << "deq["; for (auto v : vec) os << v << ','; os << ']'; return os; } template ostream &operator<<(ostream &os, const set &vec) { os << '{'; for (auto v : vec) os << v << ','; os << '}'; return os; } template ostream &operator<<(ostream &os, const unordered_set &vec) { os << '{'; for (auto v : vec) os << v << ','; os << '}'; return os; } template ostream &operator<<(ostream &os, const multiset &vec) { os << '{'; for (auto v : vec) os << v << ','; os << '}'; return os; } template ostream &operator<<(ostream &os, const unordered_multiset &vec) { os << '{'; for (auto v : vec) os << v << ','; os << '}'; return os; } template ostream &operator<<(ostream &os, const pair &pa) { os << '(' << pa.first << ',' << pa.second << ')'; return os; } template ostream &operator<<(ostream &os, const map &mp) { os << '{'; for (auto v : mp) os << v.first << "=>" << v.second << ','; os << '}'; return os; } template ostream &operator<<(ostream &os, const unordered_map &mp) { os << '{'; for (auto v : mp) os << v.first << "=>" << v.second << ','; os << '}'; return os; } #ifdef HITONANODE_LOCAL const string COLOR_RESET = "\033[0m", BRIGHT_GREEN = "\033[1;32m", BRIGHT_RED = "\033[1;31m", BRIGHT_CYAN = "\033[1;36m", NORMAL_CROSSED = "\033[0;9;37m", RED_BACKGROUND = "\033[1;41m", NORMAL_FAINT = "\033[0;2m"; #define dbg(x) cerr << BRIGHT_CYAN << #x << COLOR_RESET << " = " << (x) << NORMAL_FAINT << " (L" << __LINE__ << ") " << __FILE__ << COLOR_RESET << endl #else #define dbg(x) {} #endif // Linear sieve algorithm for fast prime factorization // Complexity: O(N) time, O(N) space: // - MAXN = 10^7: ~44 MB, 80~100 ms (Codeforces / AtCoder GCC, C++17) // - MAXN = 10^8: ~435 MB, 810~980 ms (Codeforces / AtCoder GCC, C++17) // Reference: // [1] D. Gries, J. Misra, "A Linear Sieve Algorithm for Finding Prime Numbers," // Communications of the ACM, 21(12), 999-1003, 1978. // - https://cp-algorithms.com/algebra/prime-sieve-linear.html // - https://37zigen.com/linear-sieve/ struct Sieve { std::vector min_factor; std::vector primes; Sieve(int MAXN) : min_factor(MAXN + 1) { for (int d = 2; d <= MAXN; d++) { if (!min_factor[d]) { min_factor[d] = d; primes.emplace_back(d); } for (const auto &p : primes) { if (p > min_factor[d] or d * p > MAXN) break; min_factor[d * p] = p; } } } // Prime factorization for 1 <= x <= MAXN^2 // Complexity: O(log x) (x <= MAXN) // O(MAXN / log MAXN) (MAXN < x <= MAXN^2) template std::map factorize(T x) const { std::map ret; assert(x > 0 and x <= ((long long)min_factor.size() - 1) * ((long long)min_factor.size() - 1)); for (const auto &p : primes) { if (x < T(min_factor.size())) break; while (!(x % p)) x /= p, ret[p]++; } if (x >= T(min_factor.size())) ret[x]++, x = 1; while (x > 1) ret[min_factor[x]]++, x /= min_factor[x]; return ret; } // Enumerate divisors of 1 <= x <= MAXN^2 // Be careful of highly composite numbers https://oeis.org/A002182/list // https://gist.github.com/dario2994/fb4713f252ca86c1254d#file-list-txt (n, (# of div. of n)): // 45360->100, 735134400(<1e9)->1344, 963761198400(<1e12)->6720 template std::vector divisors(T x) const { std::vector ret{1}; for (const auto p : factorize(x)) { int n = ret.size(); for (int i = 0; i < n; i++) { for (T a = 1, d = 1; d <= p.second; d++) { a *= p.first; ret.push_back(ret[i] * a); } } } return ret; // NOT sorted } // Euler phi functions of divisors of given x // Verified: ABC212 G https://atcoder.jp/contests/abc212/tasks/abc212_g // Complexity: O(sqrt(x) + d(x)) template std::map euler_of_divisors(T x) const { assert(x >= 1); std::map ret; ret[1] = 1; std::vector divs{1}; for (auto p : factorize(x)) { int n = ret.size(); for (int i = 0; i < n; i++) { ret[divs[i] * p.first] = ret[divs[i]] * (p.first - 1); divs.push_back(divs[i] * p.first); for (T a = divs[i] * p.first, d = 1; d < p.second; a *= p.first, d++) { ret[a * p.first] = ret[a] * p.first; divs.push_back(a * p.first); } } } return ret; } // Moebius function Table, (-1)^{# of different prime factors} for square-free x // return: [0=>0, 1=>1, 2=>-1, 3=>-1, 4=>0, 5=>-1, 6=>1, 7=>-1, 8=>0, ...] https://oeis.org/A008683 std::vector GenerateMoebiusFunctionTable() const { std::vector ret(min_factor.size()); for (unsigned i = 1; i < min_factor.size(); i++) { if (i == 1) { ret[i] = 1; } else if ((i / min_factor[i]) % min_factor[i] == 0) { ret[i] = 0; } else { ret[i] = -ret[i / min_factor[i]]; } } return ret; } // Calculate [0^K, 1^K, ..., nmax^K] in O(nmax) // Note: **0^0 == 1** template std::vector enumerate_kth_pows(long long K, int nmax) const { assert(nmax < int(min_factor.size())); assert(K >= 0); if (K == 0) return std::vector(nmax + 1, 1); std::vector ret(nmax + 1); ret[0] = 0, ret[1] = 1; for (int n = 2; n <= nmax; n++) { if (min_factor[n] == n) { ret[n] = MODINT(n).pow(K); } else { ret[n] = ret[n / min_factor[n]] * ret[min_factor[n]]; } } return ret; } }; Sieve sieve((1 << 20)); struct CountPrimes { // Count Primes less than or equal to x (\pi(x)) for each x = N / i (i = 1, ..., N) in O(N^(2/3)) time // Learned this algorihtm from https://old.yosupo.jp/submission/14650 // Reference: https://min-25.hatenablog.com/entry/2018/11/11/172216 using Int = long long; Int n, n2, n3, n6; std::vector is_prime; // [0, 0, 1, 1, 0, 1, 0, 1, ...] std::vector primes; // primes up to O(N^(1/2)), [2, 3, 5, 7, ...] int s; // size of vs std::vector vs; // [N, ..., n2, n2 - 1, n2 - 2, ..., 3, 2, 1] std::vector pi; // pi[i] = (# of primes s.t. <= vs[i]) is finally obtained std::vector _fenwick; int getidx(Int a) const { return a <= n2 ? s - a : n / a - 1; } // vs[i] >= a を満たす最大の i を返す void _fenwick_rec_update(int i, Int cur, bool first) { // pi[n3:] に対して cur * (primes[i] 以上の素因数) の数の寄与を減じる if (!first) { for (int x = getidx(cur) - n3; x >= 0; x -= (x + 1) & (-x - 1)) _fenwick[x]--; } for (int j = i; cur * primes[j] <= vs[n3]; j++) _fenwick_rec_update(j, cur * primes[j], false); } CountPrimes(Int n_) : n(n_), n2((Int)sqrtl(n)), n3((Int)cbrtl(n)), n6((Int)sqrtl(n3)) { is_prime.assign(n2 + 300, 1), is_prime[0] = is_prime[1] = 0; // `+ 300`: https://en.wikipedia.org/wiki/Prime_gap for (size_t p = 2; p < is_prime.size(); p++) { if (is_prime[p]) { primes.push_back(p); for (size_t j = p * 2; j < is_prime.size(); j += p) is_prime[j] = 0; } } for (Int now = n; now; now = n / (n / now + 1)) vs.push_back(now); // [N, N / 2, ..., 1], Relevant integers (decreasing) length ~= 2sqrt(N) s = vs.size(); // pi[i] = (# of integers x s.t. x <= vs[i], (x is prime or all factors of x >= p)) // pre = (# of primes less than p) // 最小の素因数 p = 2, ..., について篩っていく pi.resize(s); for (int i = 0; i < s; i++) pi[i] = vs[i] - 1; int pre = 0; auto trans = [&](int i, Int p) { pi[i] -= pi[getidx(vs[i] / p)] - pre; }; size_t ip = 0; // [Sieve Part 1] For each prime p satisfying p <= N^(1/6) (Only O(N^(1/6) / log N) such primes exist), // O(sqrt(N)) simple operation is conducted. // - Complexity of this part: O(N^(2/3) / logN) for (; primes[ip] <= n6; ip++, pre++) { const auto &p = primes[ip]; for (int i = 0; p * p <= vs[i]; i++) trans(i, p); } // [Sieve Part 2] For each prime p satisfying N^(1/6) < p <= N^(1/3), // point-wise & Fenwick tree-based hybrid update is used // - first N^(1/3) elements are simply updated by quadratic algorithm. // - Updates of latter segments are managed by Fenwick tree. // - Complexity of this part: O(N^(2/3)) (O(N^(2/3)/log N) operations for Fenwick tree (O(logN) per query)) _fenwick.assign(s - n3, 0); // Fenwick tree, inversed order (summation for upper region) auto trans2 = [&](int i, Int p) { int j = getidx(vs[i] / p); auto z = pi[j]; if (j >= n3) { for (j -= n3; j < int(_fenwick.size()); j += (j + 1) & (-j - 1)) z += _fenwick[j]; } pi[i] -= z - pre; }; for (; primes[ip] <= n3; ip++, pre++) { const auto &p = primes[ip]; for (int i = 0; i < n3 and p * p <= vs[i]; i++) trans2(i, p); // upto n3, total trans2 times: O(N^(2/3) / logN) _fenwick_rec_update(ip, primes[ip], true); // total update times: O(N^(2/3) / logN) } for (int i = s - n3 - 1; i >= 0; i--) { int j = i + ((i + 1) & (-i - 1)); if (j < s - n3) _fenwick[i] += _fenwick[j]; } for (int i = 0; i < s - n3; i++) pi[i + n3] += _fenwick[i]; // [Sieve Part 3] For each prime p satisfying N^(1/3) < p <= N^(1/2), use only simple updates. // - Complexity of this part: O(N^(2/3) / logN) // \sum_i (# of factors of vs[i] of the form p^2, p >= N^(1/3)) = \sum_{i=1}^{N^(1/3)} \pi(\sqrt(vs[i]))) // = sqrt(N) \sum_i^{N^(1/3)} i^{-1/2} / logN = O(N^(2/3) / logN) // (Note: \sum_{i=1}^{N} i^{-1/2} = O(sqrt N) https://math.stackexchange.com/questions/2600796/finding-summation-of-inverse-of-square-roots ) for (; primes[ip] <= n2; ip++, pre++) { const auto &p = primes[ip]; for (int i = 0; p * p <= vs[i]; i++) trans(i, p); } } }; namespace SPRP { // http://miller-rabin.appspot.com/ const std::vector> bases{ {126401071349994536}, // < 291831 {336781006125, 9639812373923155}, // < 1050535501 (1e9) {2, 2570940, 211991001, 3749873356}, // < 47636622961201 (4e13) {2, 325, 9375, 28178, 450775, 9780504, 1795265022} // <= 2^64 }; inline int get_id(long long n) { if (n < 291831) { return 0; } else if (n < 1050535501) { return 1; } else if (n < 47636622961201) return 2; else { return 3; } } } // namespace SPRP // Miller-Rabin primality test // https://ja.wikipedia.org/wiki/%E3%83%9F%E3%83%A9%E3%83%BC%E2%80%93%E3%83%A9%E3%83%93%E3%83%B3%E7%B4%A0%E6%95%B0%E5%88%A4%E5%AE%9A%E6%B3%95 // Complexity: O(lg n) per query struct { long long modpow(__int128 x, __int128 n, long long mod) noexcept { __int128 ret = 1; for (x %= mod; n; x = x * x % mod, n >>= 1) ret = (n & 1) ? ret * x % mod : ret; return ret; } bool operator()(long long n) noexcept { if (n < 2) return false; if (n % 2 == 0) return n == 2; int s = __builtin_ctzll(n - 1); for (__int128 a : SPRP::bases[SPRP::get_id(n)]) { if (a % n == 0) continue; a = modpow(a, (n - 1) >> s, n); bool may_composite = true; if (a == 1) continue; for (int r = s; r--; a = a * a % n) { if (a == n - 1) may_composite = false; } if (may_composite) return false; } return true; } } is_prime; int main() { lint N; cin >> N; CountPrimes p(N); lint npall = p.pi.front() + is_prime(N + 1); vector p1p; for (auto x : p.vs) p1p.push_back(is_prime(x + 1)); lint npd = 0; for (auto p : p.primes) if (p * (p - 1) <= N) npd++; vector dp(p.vs.size()); dp[0] = 1; int h = int(p.vs.size()) - 1; lint npuse = 0; auto ps_re = p.primes; vector nxt(p.vs.size(), -1); for (lint pr : ps_re) { if (pr * (pr - 1) > N) continue; npuse++; while (h >= 0 and (pr - 1) > p.vs[h]) --h; for (int i = h; i >= 0; --i) { auto tmp = dp[i]; if (pr <= p.vs[i]) { nxt[i] = p.getidx(p.vs[i] / pr); } else { nxt[i] = -1; } for (int j = p.getidx(p.vs[i] / (pr - 1)); j >= 0; j = nxt[j]) { dp[j] += tmp; if (j > h) break; } // for (lint x = p.vs[i] / (pr - 1); x; x /= pr) dp[p.getidx(x)] += tmp; } } lint ret = 0; REP(i, dp.size()) { auto ispr = [&](lint x) { if (x < sieve.min_factor.size()) return sieve.min_factor[x] == x; else return is_prime(x); }; ret += dp[i] * (max(0LL, p.pi[i] + p1p[i] - npuse) + 1); } cout << ret << '\n'; }