题目:
给定一个质数,求出这个质数是第几个质数。
输入格式:
第一行一个正整数n(1≤n≤105),
接下来n行,每行一个质数p(2≤p≤107)。
输出格式:
输出n行,每行输出一个正整数。
输入样例:
3
2
3
5
输出样例:
1
2
3
所用算法详解:
埃氏筛(Eratosthenes筛法)详解
算法原理
埃氏筛是一种高效找出一定范围内所有质数的算法。它的核心思想是:
质数的倍数一定不是质数(除了质数本身)。
算法步骤
-
假设我们想找出 1 到 N 之间的所有质数
-
先假设所有数都是质数(标记为1)
-
从2开始:
如果当前数是质数,它的倍数(2倍、3倍...)都不是质数,将它们标记为非质数
-
重复这个过程直到 N
AC代码
c
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
const int MAXN = 10000000;
int is_prime[MAXN + 10] = {0}; // 0表示未处理,正数表示质数序号,-1表示合数
int n;
void init() {
int cnt = 1;
for (int i = 2; i <= MAXN; i++) {
if (is_prime[i] == 0) { // i是质数
is_prime[i] = cnt; // 记录这是第cnt个质数
cnt++;
// 从 i*i 开始标记合数
for (int j = i; j <= MAXN / i; j++) {
is_prime[i * j] = -1; // 标记为合数
}
}
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
init();
for (int i = 1; i <= n; i++) {
int p;
cin >> p;
cout << is_prime[p];
if (i != n) cout << endl;
}
return 0;
}
代码逐行讲解
c
int is_prime[MAXN + 10] = {0}; // 0表示未处理,正数表示质数序号,-1表示合数
void init() {
int cnt = 1; // 质数计数器
// 从2开始遍历
for (int i = 2; i <= MAXN; ++i) {
if (is_prime[i] == 0) { // 如果i是质数(未被处理)
is_prime[i] = cnt; // 记录这是第cnt个质数
cnt++; // 计数器加1
// 从 i*i 开始标记i的所有倍数为合数
for (int j = i; j <= MAXN / i; j++) {
is_prime[i * j] = -1; // 标记为合数
}
}
}
}
运行过程示例
假设我们要找前几个质数(以10为例):
初始状态:
c
数字: 1 2 3 4 5 6 7 8 9 10
标记: 1 1 1 1 1 1 1 1 1 1 (1表示质数)
处理 i=2:
-
2是质数 → 标记为第1个质数
-
删除2的倍数:4、6、8、10标记为0
c
数字: 1 2 3 4 5 6 7 8 9 10
标记: 0 1 1 0 1 0 1 0 1 0
处理 i=3:
-
3是质数 → 标记为第2个质数
-
删除3的倍数:6、9标记为0
c
数字: 1 2 3 4 5 6 7 8 9 10
标记: 0 1 2 0 1 0 1 0 0 0
处理 i=4 :已经被标记为0,跳过
处理 i=5:5是质数 → 标记为第3个质数
c
数字: 1 2 3 4 5 6 7 8 9 10
标记: 0 1 2 0 3 0 1 0 0 0
以此类推...
为什么这样高效?
-
时间复杂度:O(n log log n),接近线性
-
避免了每个数都要做质数判断的重复计算
-
一次计算,多次查询(适合本题需要处理多个质数查询)
针对本题的优化
本题中:
-
需要查询 n 个质数的序号
-
质数范围是 2 ≤ p ≤ 10^7
-
用埃氏筛可以一次性计算出所有质数及其序号
代码执行流程
-
init()预处理:计算出所有质数的序号 -
对于每个输入的质数 x
-
直接输出
is_prime[x]即可得到它的序号
比如输入5,is_prime[5] = 3,直接输出3。
这种方法的优点是:
-
预处理:只需要一次计算
-
查询:O(1) 时间复杂度
-
空间换时间:用数组存储结果