文章目录
- 💯前言
- 💯题目描述
- 题目内容
- 输入格式
- 输出格式
- 题目示例
- 💯原始做法分析
- 解法源码解析
- 1. 步骤分析
- 2. 问题分析
- 3. 性能问题
- 💯老师提供的优化解法
- 优化代码
- 每部分解释
- 1. 最大检查范围优化
- 2. 基于因子对积的矩限情况
- 3. 质数计数器和条件判断
- 💯扩展与进一步优化
- 使用埃拉托色尼筛法
- 实现代码
- 性能分析
- 💯小结
💯前言
- 在计算机竞赛和线上评测中,第一次接触到一些关于质数的题目,经常会遇到质数判断和数学选择解法不充分优化并且越过时间限制的情况。本文将从一道关于质数的题目出发,细致分析题目的解法,对比我们的原始做法和老师提供的优化解法进行对比,并提出不同方向的扩展思考。通过这些分析,希望帮助读者更好地理解质数判断的实现,也能优化他们的解题能力。
C++ 参考手册
💯题目描述
B2085 第 n 小的质数
这道题目来自洛谷平台,名为“B2085 第 n 小的质数”,请系统在此总结全问题。
题目内容
输入一个正整数 n n n,求正整数范围中第 n n n 小的质数。
输入格式
一个不超过 30000 的正整数 n n n。
输出格式
第 n n n 小的质数。
题目示例
输入
10
输出
29
💯原始做法分析
在初始解法中,我使用了一种直观但效率不高的做法:通过逐个检查每个数字是否是质数,计数第 n n n 个质数就结束。以下是原始代码:
#include <iostream>
using namespace std;
int main()
{
int n = 0;
cin >> n;
int count = 0;
int i = 2;
while (1)
{
int flag = 1;
for (int j = 2; j < i; j++)
{
if (i % j == 0)
{
flag = 0;
break;
}
}
if (flag)
count++;
if (count == n)
{
cout << i << endl;
break;
}
i++;
}
return 0;
}
解法源码解析
1. 步骤分析
- 通过定义变量
n
输入需求的第 n n n 个质数。 - 以
i=2
为起始,逐步检查每个数字是否为质数:- 通过完全逐环,从 2 2 2 到 i − 1 i-1 i−1 检查是否存在因数。
- 如果能被数 j j j 整除,则表明不是质数;否则计为质数。
- 如果质数计数达到 n n n,则输出当前质数,结束程序。
2. 问题分析
原始代码实现了基础功能,但是在最大输入范围内,每次检查质数都需要在
O
(
i
)
O(i)
O(i) 处理时间。总处理时间为系列和:
O
(
2
)
+
O
(
3
)
+
O
(
4
)
+
⋯
+
O
(
k
)
=
O
(
k
2
)
O(2) + O(3) + O(4) + \dots + O(k) = O(k^2)
O(2)+O(3)+O(4)+⋯+O(k)=O(k2)
对于输入范围较大的情况,这样的处理方式显然超出了时间限制(TLE)。
3. 性能问题
- 质数判断需要对每个 i i i 重复检查,没有用到历史质数信息。
- 检查的范围是 2 2 2 到 i − 1 i-1 i−1,而实际上只需要检查到 i \sqrt{i} i ,是其中一个重要的优化场景。
- 处理质数的繁并级数增长,在输入范围较大时,会导致超时错误。
💯老师提供的优化解法
老师提出了基于 因数对积 的优化思路:如果 n n n 有一个因子 a a a,则存在一个不大于 n \sqrt{n} n 的因子 b b b。使用该思路,可以在判断质数时,尽可能减少检查范围,并尽可能推动应用性能。
优化代码
老师提供的优化代码如下:
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int n = 0; // 输入的目标:第 n 个质数
cin >> n;
int i = 2; // 从 2 开始检测质数
int c = 0; // 质数计数器
while (1) // 无限循环,直到找到第 n 个质数
{
int flag = 1; // 假设当前数 i 是质数
int j = 0; // 因子检测变量
for (j = 2; j <= sqrt(i); j++) // 从 2 遍历到 sqrt(i)
{
if (i % j == 0) // 如果 i 能被 j 整除
{
flag = 0; // 标记为非质数
break; // 退出循环
}
}
if (flag) // 如果 flag 仍为 1,说明 i 是质数
c++; // 质数计数器加 1
if (c == n) // 如果已经找到第 n 个质数
{
cout << i << endl; // 输出当前数 i
break; // 结束程序
}
i++; // 检测下一个数
}
return 0;
}
每部分解释
1. 最大检查范围优化
优化前:需要从
j
=
2
j=2
j=2 检查到
j
<
i
j < i
j<i,最均检查次数为
i
−
2
i-2
i−2。
优化后:只需要检查
j
j
j 范围为
[
2
,
i
]
[2, \sqrt{i}]
[2,i],尽可能减少计算次数。
2. 基于因子对积的矩限情况
根据计算规则,如果存在一个因子 a a a,但 a > i a > \sqrt{i} a>i,则依照因子对积规则,必然存在一个 b ≤ i b \leq \sqrt{i} b≤i。因而,只需检查尺寸最大为 i \sqrt{i} i。
3. 质数计数器和条件判断
通过 flag
表示当前数是否为质数,如果检查到任何因数,则将标记编为非质数,并退出检查循环。如果第
n
n
n 个质数计数器达成,则输出质数,退出程序。
💯扩展与进一步优化
使用埃拉托色尼筛法
如果需要更高效地找到第 n n n 个质数,可以考虑使用埃拉托色尼筛法。这是一种基于标记非质数的算法,能够快速筛选出某范围内的所有质数。
实现代码
以下是埃拉托色尼筛法的实现:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n;
cin >> n;
const int MAX = 200000; // 预估范围
vector<bool> is_prime(MAX, true);
is_prime[0] = is_prime[1] = false;
for (int i = 2; i * i < MAX; ++i)
{
if (is_prime[i])
{
for (int j = i * i; j < MAX; j += i)
is_prime[j] = false;
}
}
vector<int> primes;
for (int i = 2; i < MAX; ++i)
{
if (is_prime[i])
primes.push_back(i);
}
cout << primes[n - 1] << endl;
return 0;
}
性能分析
- 时间复杂度:筛法的时间复杂度为 O ( n log log n ) O(n \log \log n) O(nloglogn),远优于逐个检查质数的方法。
- 空间复杂度:需要 O ( n ) O(n) O(n) 的空间存储布尔数组。
💯小结
通过对题目、原始做法以及优化方案的逐步分析,我们可以看出质数问题的优化空间非常大。从逐个检查到基于 n \sqrt{n} n 的优化,再到埃拉托色尼筛法,每一步优化都能显著提升性能。对于大规模输入,使用筛法是更优的选择。而这些方法背后的数学逻辑(如因子对积的关系、筛法的基本原理)更是值得深入理解和学习的。