题目
我们知道第一个质数是 22、第二个质数是 33、第三个质数是 55……
请你计算第 20192019 个质数是多少?
运行限制
- 最大运行时间:1s
- 最大运行内存: 128M
解答
- 暴力解法
#include <iostream>
using namespace std;
int isPrime(int num) {
// 判断一个数字是不是质数,return 0代表不是质数, return 1 代表是质数
for (int i = 2; i < num; i++) {
// 循环中判断是否能被1或者它本身之外的数字整除,若能则不是质数,直接return 0
if (num % i == 0) {
return 0;
}
}
// 循环结束都没发现能整除的其他数字,可以返回1
return 1;
}
int main()
{
int count = 0; // 定义计数器,默认为0
int answer = 0;
for (int i = 2; count != 2019; i++) { // 从2开始循环是因为2是第一个质数,不要从1开始循环
if (isPrime(i) == 1) { // 若判断为是质数
count++; // 计数器++;
answer = i; // 迭代更新answer
}
}
cout << answer << endl; // 输出答案
return 0;
}
- 优化解法
#include <iostream>
using namespace std;
int main() {
int n = 2019; // 要计算的质数的位置
int num = 3; // 初始为3,因为第一个质数是2
int count = 1; // 已经找到了一个质数2,所以从1开始计数
while (count < n) {
num += 2; // 只需要判断奇数是否为质数
bool isPrime = true; // 假设当前数是质数
for (int i = 3; i <= sqrt(num); i += 2) {
if (num % i == 0) {
isPrime = false; // 如果能被整除,则不是质数
break;
}
}
if (isPrime) {
count++; // 找到一个质数
}
}
cout << num << endl; // 输出第2019个质数
return 0;
}
- 线性筛法(Linear Sieve)
线性筛法是一种求解质数的算法,可以在$O(n)$的时间复杂度内预处理出小于等于$n$的所有质数。
线性筛法的基本思想是从小到大遍历每个正整数,如果它是质数,就将它加入质数数组中,并用它来筛掉它的倍数。在遍历过程中,对于每个数只会被它的最小质因子筛去,因此每个合数只会被筛一次,从而保证了线性时间复杂度。
下面是线性筛法的伪代码:
primes = [] # 质数数组
is_prime = [True] * (n+1) # 标记是否为质数
for i in range(2, n+1):
if is_prime[i]:
primes.append(i) # 将i加入质数数组
for j in range(len(primes)):
if i * primes[j] > n:
break
is_prime[i * primes[j]] = False # 将i*primes[j]标记为非质数
if i % primes[j] == 0:
break # 如果i是primes[j]的倍数,就跳出循环
如果在内层循环中,$i$是$primes[j]$的倍数,就跳出循环,这是因为$i$已经被$primes[j]$的其他倍数筛过了,不需要重复筛。
例如,当$i$为$2 \times 3=6$时,在内层循环中,$i$会被$2$筛一次,被$3$筛一次,之后就不需要再被其他数筛了。因此,如果$i$是$primes[j]$的倍数,就可以跳出循环,不需要继续遍历$primes$数组中的其他质数。这样可以减少重复的筛选,提高算法的效率。
C++代码实现如下:
#include <iostream>
#include <vector>
using namespace std;
const int MAXN = 20000; // 估计2019个质数的上限
vector<int> primes; // 存储质数
bool isPrime[MAXN]; // 标记是否为质数
int main() {
fill(isPrime, isPrime+MAXN, true); // 初始化标记数组,全部设置为true
for (int i=2; i<MAXN; i++) {
if (isPrime[i]) { // i是质数
primes.push_back(i); // 将i加入质数数组
if (primes.size() == 2019) {
cout << primes.back() << endl;
break;
}
}
for (int j=0; j<primes.size() && i*primes[j]<MAXN; j++) {
isPrime[i*primes[j]] = false; // 将i*primes[j]标记为非质数
if (i % primes[j] == 0) break; // 如果i是primes[j]的倍数,就跳出循环
}
}
return 0;
}
可以看到,线性筛法的实现相对简单,但需要注意以下几点:
- 对于每个数只需要用它的最小质因子来筛去它的倍数,因此内层循环的终止条件为$i \times primes[j] > n$。
- 在内层循环中,如果$i$是$primes[j]$的倍数,就跳出循环,这是因为$i$已经被$primes[j]$的其他倍数筛过了,不需要重复筛。
- 在初始化标记数组时,除0和1外都初始化为True。