L7. Count Primes in a Range L-R | Maths Playlist
This chapter explores how to efficiently solve query-based prime counting problems within a given numerical range ( to ). Moving from a naive brute-force approach to a pre-computed prefix-sum optimization built on top of the Sieve of Eratosthenes, this note covers the step-by-step evolution of an optimal algorithmic design.
1. Problem Statement & Constraints
The core objective is to answer independent queries, where each query provides a range . For every query, we must output the total count of prime numbers existing inclusively between and .
Input Parameters & Scale
- Number of Queries (): Up to queries.
- Range Boundaries (): , with scaling up to .
2. Naive Approach & Its Limitations
The Brute-Force Strategy
In a straightforward implementation, we iterate through each query, initialize a counter to zero, and run an inner loop from to . For every individual number, we invoke a prime-checking function (which takes time).
Naive Pseudo-code
def solve_naive(queries):
for i in range(len(queries)):
L = queries[i][0]
R = queries[i][1]
count = 0
for j in range(L, R + 1):
if isPrime(j):
count += 1
print(count)
Complexity Analysis
- Time Complexity: , which easily exceeds hundreds of millions of operations under constraints like and . This approach results in a Time Limit Exceeded (TLE) verdict.
3. Better Approach: Sieve-Optimized Prime Checks
To remove the expensive prime check inside the query loop, we can precompute prime indicators up to the maximum possible value of () using the Sieve of Eratosthenes.
How the Sieve Improvement Works
- Generate a boolean/integer array up to where index values mark whether a number is prime (
1) or composite (0). - Inside the query loop, replace the heavy prime-validation function with a direct array lookup.
Better Approach Pseudo-code
# Precompute Sieve up to max R (10^6)
max_val = 10**6
prime_sieve = get_sieve(max_val) # Returns array where 1 = prime, 0 = composite
def solve_better(queries):
for query in queries:
L, R = query[0], query[1]
count = 0
for j in range(L, R + 1):
if prime_sieve[j] == 1:
count += 1
print(count)
Remaining Bottleneck
While we eliminated the function call, we still execute an inner loop of size for every single query. Multiplied by , this remains too slow for worst-case scenarios.
4. Optimal Approach: Prefix Sum Over Sieve
To achieve an optimal query time, we must eliminate the inner loop entirely. We can accomplish this using a Prefix Sum Array built on top of our Sieve.
Core Mental Model & Step-by-Step Transformation
Instead of storing a binary flag ( or ) at index in our sieve array, we modify the array to store the cumulative count of prime numbers from up to index .
Example Dry Run ()
-
Initial Sieve Array (0/1 flags):
CODEIndex: 0 1 2 3 4 5 6 7 8 9 10 Value: 0 0 1 1 0 1 0 1 0 0 0 -
Accumulating Prefix Sums: We maintain a running
counter = 0and iterate from index to , adding each prime flag to our running total and updating the array in place.- Index
2:count (0) + 1=1 - Index
3:count (1) + 1=2 - Index
4:count (2) + 0=2 - Index
5:count (2) + 1=3 - Index
6:count (3) + 0=3 - Index
7:count (3) + 1=4 - Index
8:count (4) + 0=4 - Index
9:count (4) + 0=4 - Index
10:count (4) + 0=4
- Index
-
Final Prefix-Sum Sieve Array:
CODEIndex: 0 1 2 3 4 5 6 7 8 9 10 Value: 0 0 1 2 2 3 3 4 4 4 4Interpretation: At index
9, the value4tells us instantly that there are exactly prime numbers between and .
5. Range Query Formula Derivation
Once our prefix sum array is ready, answering any query requires a simple subtraction:
Why This Works
PrefixSum[R]gives the total count of primes from up to .PrefixSum[L - 1]gives the unwanted count of primes from up to .- Subtracting the latter from the former leaves precisely the primes within .
Concrete Example ():
- Primes up to index (
PrefixSum[9]): - Unwanted primes up to index (
PrefixSum[2]): ( is prime) - Result: (Primes: ).
6. Complete Optimal Solution Implementation
Pseudo-code Implementation
def count_primes_in_ranges(queries):
MAX_LIMIT = 10**6
# Step 1: Get standard Sieve of Eratosthenes array (0/1 flags)
prime = get_sieve(MAX_LIMIT)
# Step 2: Transform Sieve into a Prefix Sum Array
counter = 0
for i in range(2, MAX_LIMIT + 1):
counter += prime[i]
prime[i] = counter # Overwrite with cumulative count
# Step 3: Answer queries in O(1) time
results = []
for query in queries:
L = query[0]
R = query[1]
# Range query formula: Primes[R] - Primes[L - 1]
ans = prime[R] - prime[L - 1]
results.append(ans)
return results
7. Comparative Strategy Analysis
Summary and Key Takeaways
- Query-Based Bottlenecks: When facing multiple queries (), running inner loops bounded by range limits () will always result in a Time Limit Exceeded error.
- The Power of Precomputation: Pre-calculating answers beforehand shifts heavy computation to initialization phase, enabling lightning-fast responses.
- Sieve of Eratosthenes as a Black Box: The Sieve efficiently identifies all prime numbers up to in time.
- Prefix Sum Transformation: By transforming binary sieve flags into cumulative counts, array indices directly store running totals.
- Query Reduction: Range prime queries simplify from an iterative search down to a single arithmetic subtraction:
Prime[R] - Prime[L - 1]. - Optimal Scaling: The combined time complexity becomes , easily accommodating large constraints without performance degradation.