3383. 线性筛素数
文章目录
- 题目描述
- 输入格式:
- 输出格式:
- 数据范围
- 输入样例
- 输出样例
- 方法一:埃氏筛法
- 解题思路
- 代码
- 复杂度分析:
- 方法二:欧拉筛法
- 解题思路
- 代码
- 复杂度分析:
- 两种方法对比
- 埃氏筛法
- 欧拉筛法
题目描述
给定一个范围 n,有 q 个询问,每次输出第 k 小的素数。
输入格式:
第一行包含两个正整数 n、q,分别表示查询的范围和查询的个数。
接下来 q 行每行一个正整数 k,表示查询第 k 小的素数。
输出格式:
输出 q 行,每行一个正整数表示答案。
数据范围
- 对于 100 % 的数据, n = 1 0 8 , 1 ≤ q ≤ 1 0 6 ,保证查询的素数不大于 n 对于100\%的数据,n = 10^8,1\leq q\leq10^6,保证查询的素数不大于 n 对于100%的数据,n=108,1≤q≤106,保证查询的素数不大于n
输入样例
100 5
1
2
3
4
5
输出样例
2
3
5
7
11
方法一:埃氏筛法
解题思路
假设要求 0 - 20 之间的素数。
从 2 开始遍历每个数,因为 0 和 1 都不是素数。2 是最小的素数。
isPrime 布尔数组标记当前下标是否为素数,true 是素数,false 不是素数。
prime 数组用于存放素数。
方法:
如果 isPrime[i] 为 true,说明 i 为素数,因为它不能被更小的数整除(除了 1),然后把 i 的所有倍数都给划去;
为什么 j = i 呢,而不是等于 2 呢?
因为 i * 2 到 i * (i - 1) 在之前已经被划去,为了避免重复操作,所以 j 从 i 开始。
如果 isPrime[i] 为 false,说明 i 不是素数,它已经被划去了。
刷题平台的时间限制一般为 1s 或 2s,这就要求操作次数控制在
1
0
7
10^7
107 以内。
因为埃氏筛法的时间复杂度为
O
(
n
×
l
o
g
(
l
o
g
n
)
)
O(n \times log(log\ n))
O(n×log(log n)),本题
n
=
1
0
8
n = 10^8
n=108,所需操作的次数会远远大于
1
0
7
10^7
107,从而导致超时,故只好用欧拉筛法。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1e8 + 10;
bool isPrime[N];
int n, q;
int prime[N];
int cnt = 0;
void GetPrime() {
// 将布尔数组初始化,全部置为 true,1 即为 true
memset(isPrime, 1, sizeof(isPrime));
isPrime[0] = false, isPrime[1] = false;
for(int i = 2; i <= n; i++) {
// 没有被划去过,说明 i 为素数
if(isPrime[i]) {
// 如果 i 是素数,将 i 的倍数都给划去
for(int j = i; i * j <= n; j++)
isPrime[i * j] = false;
// 把素数保存起来
prime[++cnt] = i;
}
}
}
int main() {
scanf("%d%d", &n, &q);
GetPrime();
while(q--) {
int k;
scanf("%d", &k);
printf("%d\n", prime[k]);
}
return 0;
}
复杂度分析:
- 时间复杂度: O ( n × l o g ( l o g n ) ) O(n\times log(log \ n)) O(n×log(log n))
- 空间复杂度: O ( n ) O(n) O(n)
方法二:欧拉筛法
解题思路
因为埃氏筛法在把合数筛掉的过程中,会把一个合数重复筛掉多次,会浪费时间。
而欧拉筛法只会将一个合数筛掉一次,具有线性的时间复杂度,故而又叫线性筛法。
欧拉筛法的时间复杂度为 O ( n ) O(n) O(n),本题 n = 1 0 8 n = 10^8 n=108,虽然在理论上超过了 1 0 7 10^7 107,但 1 0 8 10^8 108 以内的合数并没有这么多,因此在实际上不会有那么多的操作次数。
代码
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 1e8 + 10;
int prime[N], n, q;
bool isPrime[N];
void GetPrime() {
int cnt = 0;
memset(isPrime, 1, sizeof(isPrime));
isPrime[0] = 0, isPrime[1] = 0;
for(int i = 2; i <= n; i++) {
// 如果 i 依然为 true, 说明当前 i 没有被之前的 i 用 prime[j] 给筛掉,则 i 为素数
if(isPrime[i]) prime[++cnt] = i;
// j 循环遍历已有的素数,而且还要确保 i * prime[j] <= n
for(int j = 1; j <= cnt && i * prime[j] <= n; j++) {
// i 用 prime[j] 把 i * prime[j] 给筛掉
// 此时的 prime[j] 是 i * prime[j] 的最小质因数,在下面的例子中可以看出来
isPrime[i * prime[j]] = 0;
// 保证线性复杂度的重要条件
if(i % prime[j] == 0) break;
}
}
}
int main() {
scanf("%d%d", &n, &q);
GetPrime();
while(q--) {
int k;
scanf("%d", &k);
printf("%d\n", prime[k]);
}
return 0;
}
复杂度分析:
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
两种方法对比
求 20 以内的素数,筛掉 20 以内的合数
埃氏筛法
i 的值 | 质数表 | 筛去的数 |
---|---|---|
2 | 2、3、4、5、6、7、8、9、10 | 4、6、8、10、12、14、16、18、20 |
3 | 3、4、5、6 | 9、12、15、18 |
4 | / | / |
5 | / | / |
6 | / | / |
7 | / | / |
8 | / | / |
9 | / | / |
10 | / | / |
11 | / | / |
12 | / | / |
13 | / | / |
14 | / | / |
15 | / | / |
16 | / | / |
17 | / | / |
18 | / | / |
19 | / | / |
20 | / | / |
欧拉筛法
i 的值 | 质数表 | 筛去的数 |
---|---|---|
2 | 2 | 4 |
3 | 2、3 | 6、9 |
4 | 2 | 8 |
5 | 2、3 | 10、15 |
6 | 2 | 12 |
7 | 2 | 14 |
8 | 2 | 16 |
9 | 2 | 18 |
10 | 2 | 20 |
11 | / | / |
12 | / | / |
13 | / | / |
14 | / | / |
15 | / | / |
16 | / | / |
17 | / | / |
18 | / | / |
19 | / | / |
20 | / | / |