The “Meissel–Lehmer algorithm” is an algorithm that counts the primes in 1∼n in sublinear time.
Notation
[x] denotes the floor of x. \npk denotes the k-th prime, with p1=2. \nπ(x) denotes the number of primes in 1∼x. \nμ(x) denotes the Möbius function. \nFor a set S, #S denotes the size of S. \nδ(x) denotes the smallest prime factor of x. \nP+(x) denotes the largest prime factor of x.
The Meissel–Lehmer algorithm for computing π(x)
Define ϕ(x,a) as the number of positive integers less than x whose every prime factor is greater than pa, namely:
ϕ(x,a)=#{n≤x∣nmodp=0⟹p>pa}(1)
We further define Pk(x,a) as the number of positive integers less than x that have exactly k prime factors counted with multiplicity and whose every prime factor is greater than pa, namely:
Pk(x,a)=#{n≤x∣n=q1q2⋯qk⟹∀i,qi>pa}(2)
In particular, define P0(x,a)=1, so that:
ϕ(x,a)=P0(x,a)+P1(x,a)+⋯+Pk(x,a)+⋯
This infinite sum can in fact be written as a finite sum, because when pak>x, we have Pk(x,a)=0.
Let y be an integer satisfying x1/3≤y≤x1/2, and write a=π(y).
For k≥3, we have P1(x,a)=π(x)−a and Pk(x,a)=0, from which we obtain:
π(x)=ϕ(x,a)+a−1−P2(x,a)(3)
Thus, computing π(x) can be reduced to computing ϕ(x,a) and P2(x,a).
Computing P₂(x,a)
By equation (2), the number P2(x,a) equals the number of prime pairs satisfying y<p≤q and pq≤x, where the pair is (p,q).
First note that p∈[y+1,x]. In addition, for each p, we have q∈[p,x/p]. Therefore:
P2(x,a)=y<p≤x∑(π(px)−π(p)+1)(4)
When p∈[y+1,x], we have px∈[1,yx]. Thus, we can sieve the interval [1,yx], and then, for every prime p∈[y+1,x], compute π(px)−π(p)+1. To reduce the space complexity of this algorithm, we can use blocking with block length L. If L=y, then O(yxloglogx) can be computed in O(y) time and P2(x,a) space.
Computing ϕ(x,a)
For b≤a, consider all positive integers not exceeding x whose every prime factor is greater than pb−1. These numbers fall into two classes:
Those divisible by pb;
Those not divisible by pb.
Class 1 contains ϕ(pbx,b−1) numbers, while class two contains ϕ(x,b) numbers.
Therefore, we conclude:
Theorem 5.1: The function ϕ satisfies the following properties
ϕ(u,0)=[u](5)ϕ(x,b)=ϕ(x,b−1)−ϕ(pbx,b−1)(6)
A simple method for computing ϕ(x,a) can be derived from this theorem: repeatedly apply equation (7) until finally obtaining ϕ(u,0). This process can be viewed as creating a rooted binary tree beginning with the root node ϕ(x,a); Figure 1 illustrates this process. In this way, we obtain the following formula:
The figure above represents the binary tree for computing ϕ(x,a): the sum of the leaf weights is ϕ(x,a).
However, this requires computing too many things. Since y≥x1/3, merely counting numbers that are products of 3 primes not exceeding y would produce at least log3xx terms under this method, which does not meet our complexity requirements.
To limit the “growth” of this binary tree, we need to change the original stopping condition. This is the original stopping condition.
Stopping condition 1: If b=0, do not apply the rule to the node μ(n)ϕ(nx,b) using equation (6) again.
We replace it with a stronger stopping condition:
Stopping condition 2: If either of the following 2 conditions holds, do not apply the rule to the node μ(n)ϕ(nx,b) using equation (6) again:
b=0 and n≤y;
n>y.
According to Stopping condition 2, we divide the leaves of the original binary tree into two types:
If the leaf node μ(n)ϕ(nx,b) satisfies n≤y, it is called an ordinary leaf;
If the leaf node μ(n)ϕ(nx,b) satisfies n>y and n=mpb(m≤y), it is called a special leaf.
Therefore, we obtain:
Theorem 5.2: We have:
ϕ(x,a)=S0+S(7)
The contribution of ordinary leaves, S0, is:
S0=n≤y∑μ(n)[nx](8)
The contribution of special leaves, S, is:
S=n/δ(n)≤y≤n∑μ(n)ϕ(nx,π(δ(n))−1)(9)
Computing S0 can clearly be done in O(yloglogx) time; we now consider how to compute S.
After precomputing π(t)(t≤y), an O(x1/3) computation gives V1.
Consider how to accelerate the computation of V2. We can split the contribution of q into several intervals on each of which π(pqx) is constant. Then we only need to compute the length of each interval and the change in π(pqx) from one interval to the next.
More precisely, we first split V2 into two parts to simplify the complex condition q≤min(p2x,y):
First consider y<pqx<x1/2; the required value is π(pqx). It can be obtained by a block sieve on [1,x]. In each block, for every (p,q) satisfying the conditions, add π(pqx).
Computing W₃
For each p, divide q into several intervals on each of which π(pqx) is constant; the contribution of each interval can be computed in O(1). When a new q is reached, use the table of π(t) for t≤y to compute π(pqx). The table of primes up to y gives the π(t)<π(t+1)=π(pqx) satisfying t. Continuing in this way gives the next π(pqx) at which q changes.
Computing W₄
Compared with W3, W4 is smaller in q, so π(pqx) changes faster. At this point, using the method for computing W3 to compute W4 offers no advantage, so we directly enumerate the pairs (p,q) to compute W4.
Computing W₅
As for W3, we compute W5 in the same way.
Computing S₃
Using all primes less than x1/4, we sieve the interval [1,yx] once. When the sieve reaches pk, we have computed, for all squarefree m with δ(m)>pk, the values −μ(m)ϕ(mpkx,k−1). The sieve is performed in blocks; within each block, we maintain a binary tree to update in real time the intermediate results after sieving by all primes up to a given prime. Thus, we can find the number of values not yet sieved when the sieve reaches a given point in only O(logx) time.
Time and space complexity of the algorithm
The time and space complexity are determined by the following 3 processes:
Computing P2(x,a);
Computing W1,W2,W3,W4,W5;
Computing S3.
Complexity of computing P₂(x,y)
We already know that this process takes O(yxloglogx) time and O(y) space.
Complexity of computing W₁,W₂,W₃,W₄,W₅
The block sieve used to compute W1,W2 has length y and takes O(xloglogx) time and O(y) space.
The time complexity required to compute W1 is:
π(y2x)π(y)=O(ylog2xx)
The time complexity of computing W2 is:
Ox/y2<p≤x/y∑π(px)=O(y1/4log2xx3/4)
Therefore, the time complexity of computing W3 is:
Ox/y2<p≤x/y∑π(px)=O(y1/4log2xx3/4)
The time complexity of computing W4 is:
Ox/y<p≤x1/3∑π(px)=O(log2xx2/3)
The time complexity of computing W5 is:
Ox/y<p≤x1/3∑π(px)=O(log2xx2/3)
Complexity of computing S₃
For preprocessing: because we need to query ϕ(u,b) quickly, the ordinary sieve cannot compute it in O(1) time. We must maintain a data structure so that each query takes O(logx) time, giving a total time complexity of O(yxlogxloglogx).
For the summation: for each term in the sum for S3, we query the data structure, for a total of O(logx) queries. We also need to count the number of terms in the sum, namely the number of leaves in the binary tree. All leaves have the form ±ϕ(mpbx,b−1), where m≤y,b<π(x1/4). Therefore, the number of leaves is on the order of O(yπ(x1/4)). Hence the total time complexity for computing S3 is:
O(yxlogxloglogx+yx1/4)
Overall complexity
The space complexity of this algorithm is O(y), and its time complexity is:
O(yxloglogx+yxlogxloglogx+x1/4y+log2xx2/3)
Taking y=x1/3log3xloglogx gives the optimal time complexity O(log2xx2/3) and space complexity O(x1/3log3xloglogx).
Some improvements
Here we give improvements that reduce the constant factors of the algorithm and increase its practical efficiency.
In Stopping condition 2, we can use z in place of y, where z satisfies z>y. It can be proved that the time complexity of computing S3 can then be optimized to:
O(zxlogxloglogx+logxyx1/4+z3/2)
This also provides a good way to check the computation by changing the value of z.
For clarity, when describing the algorithm we choose to split at x1/4 when computing the total sum S, but in fact it suffices to have p≤pqx<p2 to perform the computation. This can be used without changing the asymptotic complexity.
Preprocessing with the first few primes 2,3,5 can save additional time.