Skip to content

segmented_sieve.hpp

SECTIONMath INCLUDEnoya/math/segmented_sieve.hpp

分段枚举不超过 \(n\) 的所有素数并按序回调;适合 \(n\) 较大但只需区间块内存。

\[ \displaystyle \pi(n)=\#\{p\le n: p\text{ prime}\} \]

Complexity: Time: \(O(n \log \log n + \lceil n / \mathrm{bs}\rceil \pi (\sqrt{n}))\). Space: \(O(\sqrt{n} + \mathrm{bs})\).

AC 记录:enumerate_primes。

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include。

/// @complexity Time: O(n log log n + ceil(n/bs) pi(sqrt(n))).
/// Space: O(sqrt(n) + bs).

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <vector>

namespace noya {

namespace segmented_sieve_internal {

inline std::uint64_t integer_sqrt(std::uint64_t val) {
  using u128 = unsigned __int128;
  std::uint64_t rt = std::uint64_t(std::sqrt(static_cast<long double>(val)));
  while (u128(rt + 1) * (rt + 1) <= val) {
    rt++;
  }
  while (u128(rt) * rt > val) {
    rt--;
  }
  return rt;
}

// Offsets count odd values. Return the next multiple's offset in the next block.
inline std::uint64_t strike(std::vector<unsigned char> &com, std::size_t cnt,
                            std::uint64_t p, std::uint64_t off) {
  for (; off < cnt; off += p) com[off] = true;
  return off - cnt;
}

} // namespace segmented_sieve_internal

/// @brief Call callback(index, prime) for every prime not exceeding n and
/// return pi(n). The callback is invoked in increasing prime order.
/// bs counts odd values per block; zero is treated as one.
template <class Callback>
std::uint64_t for_each_prime(std::uint64_t n, Callback &&cal,
                             std::size_t bs = 1U << 20) {
  if (n < 2) {
    return 0;
  }
  bs = std::max<std::size_t>(bs, 1);

  std::uint64_t lim = segmented_sieve_internal::integer_sqrt(n);
  std::vector<std::uint64_t> ps;
  {
    std::vector<unsigned char> cmp(lim + 1);
    for (std::uint64_t val = 2; val <= lim; val++) {
      if (cmp[val]) continue;
      if (val > 2) ps.push_back(val);
      if (val <= lim / val) {
        for (std::uint64_t mul = val * val; mul <= lim; mul += val)
          cmp[mul] = true;
      }
    }
  }

  std::uint64_t np = 0;
  cal(np++, std::uint64_t(2));
  std::vector<std::uint64_t> nxt(ps.size());
  std::size_t active = 0;
  std::vector<unsigned char> com(std::min<std::uint64_t>(bs, (n - 1) / 2));
  for (std::uint64_t l = 3; l <= n;) {
    std::uint64_t ava = (n - l) / 2 + 1;
    std::size_t cnt =
        std::size_t(std::min<std::uint64_t>(ava, std::uint64_t(bs)));
    std::uint64_t r = l + 2 * (cnt - 1);
    std::fill_n(com.begin(), cnt, 0);
    while (active < ps.size() && ps[active] <= r / ps[active]) {
      // A prime becomes active in the block containing its square.
      nxt[active] = (ps[active] * ps[active] - l) / 2;
      ++active;
    }
    for (std::size_t j = 0; j < active; ++j)
      nxt[j] = segmented_sieve_internal::strike(com, cnt, ps[j], nxt[j]);

    for (std::size_t off = 0; off < cnt; off++) {
      if (!com[off]) {
        cal(np++, l + 2 * off);
      }
    }
    if (r == n || r + 2 < r) {
      break;
    }
    l = r + 2;
  }
  return np;
}

} // namespace noya

 // NOYA_SEGMENTED_SIEVE_HPP
#ifndef NOYA_SEGMENTED_SIEVE_HPP
#define NOYA_SEGMENTED_SIEVE_HPP 1

/// @complexity Time: O(n log log n + ceil(n/bs) pi(sqrt(n))).
/// Space: O(sqrt(n) + bs).

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <vector>

namespace noya {

namespace segmented_sieve_internal {

inline std::uint64_t integer_sqrt(std::uint64_t val) {
  using u128 = unsigned __int128;
  std::uint64_t rt = std::uint64_t(std::sqrt(static_cast<long double>(val)));
  while (u128(rt + 1) * (rt + 1) <= val) {
    rt++;
  }
  while (u128(rt) * rt > val) {
    rt--;
  }
  return rt;
}

// Offsets count odd values. Return the next multiple's offset in the next block.
inline std::uint64_t strike(std::vector<unsigned char> &com, std::size_t cnt,
                            std::uint64_t p, std::uint64_t off) {
  for (; off < cnt; off += p) com[off] = true;
  return off - cnt;
}

} // namespace segmented_sieve_internal

/// @brief Call callback(index, prime) for every prime not exceeding n and
/// return pi(n). The callback is invoked in increasing prime order.
/// bs counts odd values per block; zero is treated as one.
template <class Callback>
std::uint64_t for_each_prime(std::uint64_t n, Callback &&cal,
                             std::size_t bs = 1U << 20) {
  if (n < 2) {
    return 0;
  }
  bs = std::max<std::size_t>(bs, 1);

  std::uint64_t lim = segmented_sieve_internal::integer_sqrt(n);
  std::vector<std::uint64_t> ps;
  {
    std::vector<unsigned char> cmp(lim + 1);
    for (std::uint64_t val = 2; val <= lim; val++) {
      if (cmp[val]) continue;
      if (val > 2) ps.push_back(val);
      if (val <= lim / val) {
        for (std::uint64_t mul = val * val; mul <= lim; mul += val)
          cmp[mul] = true;
      }
    }
  }

  std::uint64_t np = 0;
  cal(np++, std::uint64_t(2));
  std::vector<std::uint64_t> nxt(ps.size());
  std::size_t active = 0;
  std::vector<unsigned char> com(std::min<std::uint64_t>(bs, (n - 1) / 2));
  for (std::uint64_t l = 3; l <= n;) {
    std::uint64_t ava = (n - l) / 2 + 1;
    std::size_t cnt =
        std::size_t(std::min<std::uint64_t>(ava, std::uint64_t(bs)));
    std::uint64_t r = l + 2 * (cnt - 1);
    std::fill_n(com.begin(), cnt, 0);
    while (active < ps.size() && ps[active] <= r / ps[active]) {
      // A prime becomes active in the block containing its square.
      nxt[active] = (ps[active] * ps[active] - l) / 2;
      ++active;
    }
    for (std::size_t j = 0; j < active; ++j)
      nxt[j] = segmented_sieve_internal::strike(com, cnt, ps[j], nxt[j]);

    for (std::size_t off = 0; off < cnt; off++) {
      if (!com[off]) {
        cal(np++, l + 2 * off);
      }
    }
    if (r == n || r + 2 < r) {
      break;
    }
    l = r + 2;
  }
  return np;
}

} // namespace noya

#endif // NOYA_SEGMENTED_SIEVE_HPP
/// @complexity Time: O(n log log n + ceil(n/bs) pi(sqrt(n))).
/// Space: O(sqrt(n) + bs).

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <vector>

namespace noya {

namespace segmented_sieve_internal {

inline std::uint64_t integer_sqrt(std::uint64_t val) {
  using u128 = unsigned __int128;
  std::uint64_t rt = std::uint64_t(std::sqrt(static_cast<long double>(val)));
  while (u128(rt + 1) * (rt + 1) <= val) {
    rt++;
  }
  while (u128(rt) * rt > val) {
    rt--;
  }
  return rt;
}

// Offsets count odd values. Return the next multiple's offset in the next block.
inline std::uint64_t strike(std::vector<unsigned char> &com, std::size_t cnt,
                            std::uint64_t p, std::uint64_t off) {
  for (; off < cnt; off += p) com[off] = true;
  return off - cnt;
}

} // namespace segmented_sieve_internal

/// @brief Call callback(index, prime) for every prime not exceeding n and
/// return pi(n). The callback is invoked in increasing prime order.
/// bs counts odd values per block; zero is treated as one.
template <class Callback>
std::uint64_t for_each_prime(std::uint64_t n, Callback &&cal,
                             std::size_t bs = 1U << 20) {
  if (n < 2) {
    return 0;
  }
  bs = std::max<std::size_t>(bs, 1);

  std::uint64_t lim = segmented_sieve_internal::integer_sqrt(n);
  std::vector<std::uint64_t> ps;
  {
    std::vector<unsigned char> cmp(lim + 1);
    for (std::uint64_t val = 2; val <= lim; val++) {
      if (cmp[val]) continue;
      if (val > 2) ps.push_back(val);
      if (val <= lim / val) {
        for (std::uint64_t mul = val * val; mul <= lim; mul += val)
          cmp[mul] = true;
      }
    }
  }

  std::uint64_t np = 0;
  cal(np++, std::uint64_t(2));
  std::vector<std::uint64_t> nxt(ps.size());
  std::size_t active = 0;
  std::vector<unsigned char> com(std::min<std::uint64_t>(bs, (n - 1) / 2));
  for (std::uint64_t l = 3; l <= n;) {
    std::uint64_t ava = (n - l) / 2 + 1;
    std::size_t cnt =
        std::size_t(std::min<std::uint64_t>(ava, std::uint64_t(bs)));
    std::uint64_t r = l + 2 * (cnt - 1);
    std::fill_n(com.begin(), cnt, 0);
    while (active < ps.size() && ps[active] <= r / ps[active]) {
      // A prime becomes active in the block containing its square.
      nxt[active] = (ps[active] * ps[active] - l) / 2;
      ++active;
    }
    for (std::size_t j = 0; j < active; ++j)
      nxt[j] = segmented_sieve_internal::strike(com, cnt, ps[j], nxt[j]);

    for (std::size_t off = 0; off < cnt; off++) {
      if (!com[off]) {
        cal(np++, l + 2 * off);
      }
    }
    if (r == n || r + 2 < r) {
      break;
    }
    l = r + 2;
  }
  return np;
}

} // namespace noya

 // NOYA_SEGMENTED_SIEVE_HPP