Sieve Methods

Prime Sieves

Introduction

How many primes are there less than or equal to nn?

A natural idea is to test each number less than or equal to nn for primality once. This brute-force approach clearly cannot achieve optimal complexity.

Sieve of Eratosthenes

Process

Consider the following fact: for any positive integer greater than 11, let it be nn; its xx-multiple is composite when x>1x > 1. Using this fact, we can avoid many unnecessary tests.

If we process the numbers from small to large and, for each current number, mark all of its multiples greater than itself as composite, then the numbers that remain unmarked when the process ends are exactly the primes.

Implementation

C++

vector<int> prime;
bool is_prime[N];

void Eratosthenes(int n) {
  is_prime[0] = is_prime[1] = false;
  for (int i = 2; i <= n; ++i) is_prime[i] = true;
  for (int i = 2; i <= n; ++i) {
    if (is_prime[i]) {
      prime.push_back(i);
      if ((long long)i * i > n) continue;
      for (int j = i * i; j <= n; j += i)
        // We have already sieved multiples of 2 through i - 1,
        // so start directly from multiples of i here to improve the running time.
        is_prime[j] = false;  // None of the multiples of i is prime.
    }
  }
}

Python

prime = []
is_prime = [False] * N

def Eratosthenes(n):
    is_prime[0] = is_prime[1] = False
    for i in range(2, n + 1):
        is_prime[i] = True
    for i in range(2, n + 1):
        if is_prime[i]:
            prime.append(i)
            if i * i > n:
                continue
            for j in range(i * i, n + 1, i):
                is_prime[j] = False

The above is the sieve of Eratosthenes, also known as the Eratosthenes sieve or the Eulerian abbreviation in Chinese usage. Its time complexity is O(nlog⁡log⁡n)O(n\log\log n).

Proof

Let us derive this bound.

If each operation on the array costs one unit of time, the time complexity is

O(∑k=1π(n)npk)=O(n∑k=1π(n)1pk)O\left(\sum_{k=1}^{\pi(n)}{\frac{n}{p_k}}\right)=O\left(n\sum_{k=1}^{\pi(n)}{\frac{1}{p_k}}\right)

Here, pkp_k denotes the kk-th smallest prime, π(n)\pi(n) denotes the number of primes satisfying ≤n\le n, and ∑k=1π(n)\sum_{k=1}^{\pi(n)} corresponds to the first for loop. Its upper bound π(n)\pi(n) is the number of times the if (prime[i]) branch is entered, while npk\frac{n}{p_k} is the number of iterations of the second for loop.

By the second theorem of Mertens, there is a constant B1B_1 such that

∑k=1π(n)1pk=log⁡log⁡n+B1+O(1log⁡n)\sum_{k=1}^{\pi(n)}{\frac{1}{p_k}}=\log\log n+B_1+O\left(\frac{1}{\log n}\right)

Therefore, the time complexity of the sieve of Eratosthenes is O(nlog⁡log⁡n)O(n\log\log n). We next prove the weakened form ∑k≤π(n)1/pk=O(log⁡log⁡n)\sum_{k\le\pi(n)}1/p_k=O(\log\log n) of the second theorem of Mertens:

Since π(n)=Θ(n/log⁡n)\pi(n)=\Theta(n/\log n), the nn-th prime has magnitude Θ(nlog⁡n)\Theta(n\log n). Hence

∑k=1π(n)1pk=O(∑k=2π(n)1klog⁡k)=O(∫2π(n)dxxlog⁡x)=O(log⁡log⁡π(n))=O(log⁡log⁡n)\begin{aligned} \sum_{k=1}^{\pi(n)}{\frac{1}{p_k}} &=O\left(\sum_{k=2}^{\pi(n)}{\frac{1}{k\log k}}\right) \\ &=O\left(\int_2^{\pi(n)}{\frac{\mathrm dx}{x\log x}}\right) \\ &=O(\log\log\pi(n))=O(\log\log n) \end{aligned}

The method above is still not as efficient as it could be. The following methods can improve its practical running time somewhat.

Sieve Only up to the Square Root

Clearly, to find all primes up to nn, it is enough to sieve only primes no greater than n\sqrt n.

C++

vector<int> prime;
bool is_prime[N];

void Eratosthenes(int n) {
  is_prime[0] = is_prime[1] = false;
  for (int i = 2; i <= n; ++i) is_prime[i] = true;
  // i * i <= n means i <= sqrt(n).
  for (int i = 2; i * i <= n; ++i) {
    if (is_prime[i])
      for (int j = i * i; j <= n; j += i) is_prime[j] = false;
  }
  for (int i = 2; i <= n; ++i)
    if (is_prime[i]) prime.push_back(i);
}

Python

prime = []
is_prime = [False] * N

def Eratosthenes(n):
    is_prime[0] = is_prime[1] = False
    for i in range(2, n + 1):
        is_prime[i] = True
    # Let i run only while i <= sqrt(n).
    for i in range(2, isqrt(n) + 1):  # `isqrt` was added in Python 3.8.
        if is_prime[i]:
            for j in range(i * i, n + 1, i):
                is_prime[j] = False
    for i in range(2, n + 1):
        if is_prime[i]:
            prime.append(i)

This optimization does not change the asymptotic time complexity. Repeating the proof above gives nln⁡ln⁡n+o(n)n \ln \ln \sqrt n + o(n), which is asymptotically equivalent by the properties of logarithms, but the number of operations is significantly smaller.

Sieve Only Odd Numbers

Since every even number other than 2 is composite, we can skip even numbers and consider only odd numbers.

This halves the memory requirement and also roughly halves the number of required operations.

Reducing Memory Usage

During sieving, we only need an array of type bool. One element of a bool array generally occupies 11 byte (that is, 88 bits), although storing a Boolean value requires only 11 bit.

Using the relevant knowledge of bit operations, we can pack each Boolean value into one bit. We then need only nn bits (that is, n8\dfrac n 8 bytes), rather than nn bytes, which can significantly reduce memory usage. This method is called bit-level compression.

It is worth mentioning that data structures such as C++'s vector<bool> and bitset<> perform bit-level compression automatically.

In addition, vector<bool> and bitset<> offer constant-factor optimizations. After optimization with bitset<> or vector<bool>, an Eratosthenes sieve with time complexity O(nlog⁡log⁡n)O(n \log \log n) can even outperform an Euler sieve with time complexity O(n)O(n).

See bitset: Combining It with the Sieve of Eratosthenes.

Segmented Sieve

The optimization of sieving only up to the square root shows that we do not need to keep the entire is_prime[1...n] array at all times. To perform the sieve, it is enough to retain the primes up to n\sqrt n, namely prime[1...sqrt(n)], and divide the whole range into blocks, sieving each block separately. This avoids keeping multiple blocks in memory at the same time and also lets the CPU make better use of its cache.

Let ss be a constant determining the block size. There are then ⌈ns⌉\lceil {\frac n s} \rceil blocks, and block kk (k=0…⌊ns⌋k = 0 \dots \lfloor {\frac n s} \rfloor) contains the numbers in the interval [ks,ks+s−1][ks, ks + s - 1]. We can process the blocks in order; for each block kk, we traverse all primes from 11 to n\sqrt n and use them to sieve the block.

The first block requires a slight adjustment. First, all primes in [1,n][1, \sqrt n] should be retained; second, the numbers 00 and 11 should be marked as non-prime. When processing the last block, do not forget that the final number nn need not be at the end of the block.

The following implementation uses a segmented sieve to count the primes less than or equal to nn.

Implementation
int count_primes(int n) {
  constexpr static int S = 10000;
  vector<int> primes;
  int nsqrt = sqrt(n);
  vector<char> is_prime(nsqrt + 1, true);
  for (int i = 2; i <= nsqrt; i++) {
    if (is_prime[i]) {
      primes.push_back(i);
      for (int j = i * i; j <= nsqrt; j += i) is_prime[j] = false;
    }
  }
  int result = 0;
  vector<char> block(S);
  for (int k = 0; k * S <= n; k++) {
    fill(block.begin(), block.end(), true);
    int start = k * S;
    for (int p : primes) {
      int start_idx = (start + p - 1) / p;
      int j = max(start_idx, p) * p - start;
      for (; j < S; j += p) block[j] = false;
    }
    if (k == 0) block[0] = block[1] = false;
    for (int i = 0; i < S && start + i <= n; i++) {
      if (block[i]) result++;
    }
  }
  return result;
}

The asymptotic time complexity of a segmented sieve is the same as that of the sieve of Eratosthenes (unless the blocks are extremely small), but its memory usage is reduced to O(n+S)O(\sqrt{n} + S) and it has better cache behavior. On the other hand, it performs a division for every pair consisting of a block and a prime in [1,n][1, \sqrt{n}], which is particularly unfavorable for small blocks. Therefore, the constant SS must be chosen as a balance between these factors.

A block size SS between 10410^4 and 10510^5 generally gives the best speed.

Linear Sieve

The sieve of Eratosthenes can still be optimized: it marks a composite number multiple times. Is there a way to eliminate these meaningless repeated operations? Yes.

If every composite number can be marked exactly once, the time complexity can be reduced to O(n)O(n).

Implementation

C++

vector<int> pri;
bool not_prime[N];

void pre(int n) {
  for (int i = 2; i <= n; ++i) {
    if (!not_prime[i]) {
      pri.push_back(i);
    }
    for (int pri_j : pri) {
      if (i * pri_j > n) break;
      not_prime[i * pri_j] = true;
      if (i % pri_j == 0) {
        // i % pri_j == 0
        // In other words, i has already been sieved by pri_j.
        // Since the primes in pri are in increasing order, multiplying i by any other prime would produce a number that is certainly
        // sieved by a multiple of pri_j, so it need not be sieved here; simply break
        // here.
        break;
      }
    }
  }
}

Python

pri = []
not_prime = [False] * N


def pre(n):
    for i in range(2, n + 1):
        if not not_prime[i]:
            pri.append(i)
        for pri_j in pri:
            if i * pri_j > n:
                break
            not_prime[i * pri_j] = True
            if i % pri_j == 0:
                """
                i % pri_j == 0
                In other words, i has already been sieved by pri_j.
                Because the primes in pri are ordered increasingly, products of i with later primes will certainly be
                removed as multiples of pri_j. There is no need to sieve them here first, so break
                immediately.
                """
                break

The above linear sieve is also called the Euler sieve.

Note

Notice that while the sieve finds primes, it also obtains the smallest prime factor of every number.

Computing Euler's Totient Function with a Sieve

In the linear sieve, every composite number is eliminated by its smallest prime factor. For example, let p1p_1 be the smallest prime factor of nn and let n′=np1n' = \frac{n}{p_1}. During the linear sieve, nn is eliminated through n′×p1n' \times p_1.

Examining the linear sieve process, we need to handle two cases. We distinguish them according to n′ mod p1n' \bmod p_1.

If n′ mod p1=0n' \bmod p_1 = 0, then n′n' contains all the prime factors of nn.

φ(n)=n×∏i=1spi−1pi=p1×n′×∏i=1spi−1pi=p1×φ(n′)\begin{aligned} \varphi(n) & = n \times \prod_{i = 1}^s{\frac{p_i - 1}{p_i}} \\\\ & = p_1 \times n' \times \prod_{i = 1}^s{\frac{p_i - 1}{p_i}} \\\\ & = p_1 \times \varphi(n') \end{aligned}

If n′ mod p1≠0n' \bmod p_1 \neq 0, then n′n' and p1p_1 are coprime. By the properties of Euler's totient function,

φ(n)=φ(p1)×φ(n′)=(p1−1)×φ(n′)\begin{aligned} \varphi(n) & = \varphi(p_1) \times \varphi(n') \\\\ & = (p_1 - 1) \times \varphi(n') \end{aligned}

Implementation

C++

vector<int> pri;
bool not_prime[N];
int phi[N];

void pre(int n) {
  phi[1] = 1;
  for (int i = 2; i <= n; i++) {
    if (!not_prime[i]) {
      pri.push_back(i);
      phi[i] = i - 1;
    }
    for (int pri_j : pri) {
      if (i * pri_j > n) break;
      not_prime[i * pri_j] = true;
      if (i % pri_j == 0) {
        phi[i * pri_j] = phi[i] * pri_j;
        break;
      }
      phi[i * pri_j] = phi[i] * phi[pri_j];
    }
  }
}

Python

pri = []
not_prime = [False] * N
phi = [0] * N

def pre(n):
    phi[1] = 1
    for i in range(2, n + 1):
        if not not_prime[i]:
            pri.append(i)
            phi[i] = i - 1
        for pri_j in pri:
            if i * pri_j > n:
                break
            not_prime[i * pri_j] = True
            if i % pri_j == 0:
                phi[i * pri_j] = phi[i] * pri_j
                break
            phi[i * pri_j] = phi[i] * phi[pri_j]

Computing the Möbius Function with a Sieve

Definition

By the definition of the Möbius function, let nn be composite, let p1p_1 be the smallest prime factor of nn, and let n′=np1n'=\frac{n}{p_1}. Then

μ(n)={0n′ mod p1=0−μ(n′)otherwise\mu(n)= \begin{cases} 0 & n' \bmod p_1 = 0\\\\ -\mu(n') & \text{otherwise} \end{cases}

If nn is prime, then μ(n)=−1\mu(n)=-1.

Implementation

C++

vector<int> pri;
bool not_prime[N];
int mu[N];

void pre(int n) {
  mu[1] = 1;
  for (int i = 2; i <= n; ++i) {
    if (!not_prime[i]) {
      mu[i] = -1;
      pri.push_back(i);
    }
    for (int pri_j : pri) {
      if (i * pri_j > n) break;
      not_prime[i * pri_j] = true;
      if (i % pri_j == 0) {
        mu[i * pri_j] = 0;
        break;
      }
      mu[i * pri_j] = -mu[i];
    }
  }
}

Python

pri = []
not_prime = [False] * N
mu = [0] * N

def pre(n):
    mu[1] = 1
    for i in range(2, n + 1):
        if not not_prime[i]:
            pri.append(i)
            mu[i] = -1
        for pri_j in pri:
            if i * pri_j > n:
                break
            not_prime[i * pri_j] = True
            if i % pri_j == 0:
                mu[i * pri_j] = 0
                break
            mu[i * pri_j] = -mu[i]

Computing the Number of Divisors with a Sieve

Let did_i denote the number of divisors of ii, and let numinum_i denote the number of occurrences of the smallest prime factor of ii.

Divisor-Count Theorem

Theorem: if n=∏i=1mpicin=\prod_{i=1}^m p_i^{c_i}, then di=∏i=1m(ci+1)d_i=\prod_{i=1}^m (c_i+1).

Proof: the divisors of picip_i^{c_i} are pi0,pi1,…,picip_i^0,p_i^1,\dots ,p_i^{c_i}, for a total of ci+1c_i+1. By the multiplication principle, the number of divisors of nn is therefore ∏i=1m(ci+1)\prod_{i=1}^m (c_i+1).

Implementation

Since did_i is a multiplicative function, we can use a linear sieve.

Here is a brief description of the principle behind the implementation.

  1. When ii is prime, set numi←1,di←2\textit{num}_i \gets 1,\textit{d}_i \gets 2. Also let q=⌊ip⌋q = \left\lfloor \dfrac {i}{p} \right\rfloor, where pp is the smallest prime factor of ii.
  2. When pp is a prime factor of qq, set numi←numq+1,di←dqnumi×(numi+1)\textit{num}_i \gets \textit{num}_q + 1,\textit{d}_i \gets \dfrac{\textit{d}_q}{\textit{num}_i} \times (\textit{num}_i + 1).
  3. When p,qp,q are coprime, set numi←1,di←dq×(numi+1)\textit{num}_i \gets 1,\textit{d}_i \gets \textit{d}_q \times (\textit{num}_i+1).

C++

vector<int> pri;
bool not_prime[N];
int d[N], num[N];

void pre(int n) {
  d[1] = 1;
  for (int i = 2; i <= n; ++i) {
    if (!not_prime[i]) {
      pri.push_back(i);
      d[i] = 2;
      num[i] = 1;
    }
    for (int pri_j : pri) {
      if (i * pri_j > n) break;
      not_prime[i * pri_j] = true;
      if (i % pri_j == 0) {
        num[i * pri_j] = num[i] + 1;
        d[i * pri_j] = d[i] / num[i * pri_j] * (num[i * pri_j] + 1);
        break;
      }
      num[i * pri_j] = 1;
      d[i * pri_j] = d[i] * 2;
    }
  }
}

Python

pri = []
not_prime = [False] * N
d = [0] * N
num = [0] * N

def pre(n):
    d[1] = 1
    for i in range(2, n + 1):
        if not not_prime[i]:
            pri.append(i)
            d[i] = 2
            num[i] = 1
        for pri_j in pri:
            if i * pri_j > n:
                break
            not_prime[i * pri_j] = True
            if i % pri_j == 0:
                num[i * pri_j] = num[i] + 1
                d[i * pri_j] = d[i] // num[i * pri_j] * (num[i * pri_j] + 1)
                break
            num[i * pri_j] = 1
            d[i * pri_j] = d[i] * 2

Computing the Sum of Divisors with a Sieve

fif_i denotes the sum of the divisors of ii, and gig_i denotes, for the smallest prime factor of ii, the sum p0+p1+p2+…pkp^0+p^1+p^2+\dots p^k.

Implementation

C++

vector<int> pri;
bool not_prime[N];
int g[N], f[N];

void pre(int n) {
  g[1] = f[1] = 1;
  for (int i = 2; i <= n; ++i) {
    if (!not_prime[i]) {
      pri.push_back(i);
      g[i] = i + 1;
      f[i] = i + 1;
    }
    for (int pri_j : pri) {
      if (i * pri_j > n) break;
      not_prime[i * pri_j] = true;
      if (i % pri_j == 0) {
        g[i * pri_j] = g[i] * pri_j + 1;
        f[i * pri_j] = f[i] / g[i] * g[i * pri_j];
        break;
      }
      f[i * pri_j] = f[i] * f[pri_j];
      g[i * pri_j] = 1 + pri_j;
    }
  }
}

Python

pri = []
not_prime = [False] * N
f = [0] * N
g = [0] * N

def pre(n):
    g[1] = f[1] = 1
    for i in range(2, n + 1):
        if not not_prime[i]:
            pri.append(i)
            g[i] = i + 1
            f[i] = i + 1
        for pri_j in pri:
            if i * pri_j > n:
                break
            not_prime[i * pri_j] = True
            if i % pri_j == 0:
                g[i * pri_j] = g[i] * pri_j + 1
                f[i * pri_j] = f[i] // g[i] * g[i * pri_j]
                break
            f[i * pri_j] = f[i] * f[pri_j]
            g[i * pri_j] = 1 + pri_j

General Multiplicative Functions

Suppose a multiplicative function ff has the property that, for every prime pp and positive integer kk, in time polynomial in kk we can compute f(pk)f(p^k); then in O(n)O(n) time we can sieve the values f(1),f(2),…,f(n)f(1),f(2),\dots,f(n).

Suppose the prime factorization of a composite number nn is ∏i=1kpiαi\prod_{i=1}^k p_i^{\alpha_i}, where p1<p2<⋯<pkp_1<p_2<\dots<p_k are primes. In the linear sieve, record gn=p1α1g_n=p_1^{\alpha_1}. If nn is eliminated by x⋅px\cdot p, where pp is prime, then gg satisfies

gn={gx⋅px mod p=0potherwiseg_n= \begin{cases} g_x\cdot p & x\bmod p=0\\\\ p & \text{otherwise} \end{cases}

If n=gnn=g_n, then nn is a power of a prime, so f(n)f(n) can be computed in O(1)O(1) time. Otherwise, f(n)=f(ngn)⋅f(gn)f(n)=f(\frac{n}{g_n})\cdot f(g_n).

Part of this section is adapted from the blog post Решето Эратосфена and its English-language edition Sieve of Eratosthenes. The Russian version is licensed under Public Domain + Leave a Link; the English version is licensed under CC-BY-SA 4.0.

This content comes from OI Wiki: Sieve Methods. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.