线性筛法来判断素数
这里有个题目: 让我们求1到N中素数的个数。
1.暴力求素数
判断一个数是不是素数,有很多方法。在学习过程中,首先掌握的是遍历判断。
int main(){
int count=0,manx;
cin>>manx;
for(int i=1;i<=manx;i++){
if(IsPri(i))count++;
}
return 0;
}
int IsPri(int n)
{
int i=2;
if(n==1)return 0;
while(i<n){
if(n%i==0)break;
i++;
}
return (i >= n ? 1 : 0);
}
上述这种方法固然正确,但是时间复杂度太高,在计算较多、较大的数据时效率较低,在oj提交答案时经常会超时。因此介绍下面一些近似线性的筛法以及线性筛法来判断素数。
2.埃氏筛法
埃拉托斯特尼筛法,简称埃氏筛或爱氏筛,是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定素数的算法。要得到自然数n以内的全部素数,必须把不大于 n \sqrt{n} n的所有素数的倍数剔除,剩下的就是素数。
埃氏筛法的时间复杂度为 O(nlglgn), 已经很接近线性了,也是我们在刷题时较为常用的方法之一。
根据埃氏筛法,要得到自然数n以内的全部素数,必须把不大于
n
\sqrt{n}
n的所有素数的倍数剔除,剩下的就是素数。要来实现它,我们要借助一个数组 pri[manx]
,用它来标记某个数是否为素数(是否为素数的倍数)。
- 首先,
int pri[manx];
全是0,当pri[i]
被标记为1的时候,表示i为合数。 - 我们从2开始遍历,
for(i=2;i<=n/i;i++)
,注意这里有个要遍历到 n \sqrt{n} n ,所以写成i<=n/i
。 - 下面要注意了,根据我们的思路,如果
pri[i]==0
,则表示i为素数,那么i的倍数全为合数,即我们要把 pri[i的倍数] 设置为1。
if(pri[i]==0)
for(int j=i*2;j<=n;j++) //这里仍可以优化
pri[j]=1;
注意,这里仍然可以优化。当pri[i]==0的时候,表示i为素数,那么i的倍数j可以表示为 i*2
,i*3
,i*4
··· 为了便于理解,读者现在可以假设i为5 。但是,要注意到, i*2
已经是2的倍数了,当i为2时就已经,i*2
就已经被标记为合数了,i*3
也被3标记为合数了,i*4
可以表示为(i*2)*2
,被2标记为合数······如此因此,我们要从i*i
开始标记合数,因为i的2倍到i的i-1被都已经被标记了,
如下,为改进后的埃氏筛法代码:
const int manx = 1e8;
int pri[maxn]; //当pri[i]为1时,表示i为合数,默认全为0
cin>>n; //筛选从1到n的所以素数
for(int i=2;i<=n/i;i++)
if(!pri[i])
for(int j=i*i;j<=n;j+=i) pri[j]=1;
//循环结束之后,可以得知,pri[i]==0 表示i为素数
3.欧拉筛法
欧拉筛法的时间复杂度为 O(n) ,线性复杂度可以大大减少程序运行时间,同时,欧拉筛法把素数单独存放在一个数组中,对后续程序的设计有很大的帮助。
- 在这里,pri[] 数组存在的意义和埃氏筛法一样,主要是来标记某个数是否为素数。例如,若
pri[k]==1;
则k为合数,否则为素数。 - 然后我们用变量 i 从2开始遍历,
for(int i=2;i<=n;i++){ ··· }
- 此外,我们引入一个新的数组
int primes[manx];
和索引int pp=0;
里面用来存储素数。因此,会有如下语句:if(pri[i]==0) primes[pp++]=i;
因此,primes[]的储存的都是素数。 - 然后我们对素数的倍数进行标记(因为素数的倍数(2倍、3倍等等)一定是合数)。我们从素数的2倍开始标记。就如下面的代码片段所示,primes[j] 是已知的素数,而i又是我们的循环变量,从2一直取值到n,所以,primes[j]*i就一定是个合数,因此对应的pri数组要被标记:
pri[primes[j]*i]=1;
- 这一步是关键。我们对当前的 i 进行判断,看看它是不是某个合数对应的最小的质因子:
if(i%primes[j]==0)break;
例如当i取值为4时,pri[4]==1, primes[]的4倍,primes[]*i会被标记为合数,然后通过 if() 语句发现,i是某个素数的倍数,此时我们跳出这个循环,因为i可以被更小的质因子替代。就是通过这种方法,我们让每一个合数被它的最小质因子筛掉,避免了筛选的重复,减少了时间复杂度。
const int maxn = 1e8;
int pri[maxn],primes[maxn],p=0;
cin>>n;
for(int i=2;i<=n;i++){
if(!pri[i])primes[++p]=i;
for(j=1;primes[j]*i<=n;j++){
pri[primes[j]*i]=1;
if(i%primes[j]==0)break;
}
}
//循环结束之后,primes[]数组内全部是素数
下面看一个模板题:洛谷“线性筛素数”
4.【模板题】线性筛素数
题目背景
本题已更新,从判断素数改为了查询第
k
k
k 小的素数
提示:如果你使用 cin
来读入,建议使用 std::ios::sync_with_stdio(0)
来加速。
题目描述
如题,给定一个范围 n n n,有 q q q 个询问,每次输出第 k k k 小的素数。
** 输入格式**
第一行包含两个正整数 n , q n,q n,q,分别表示查询的范围和查询的个数。
接下来 q q q 行每行一个正整数 k k k,表示查询第 k k k 小的素数。
输出格式
输出 q q q 行,每行一个正整数表示答案。
输入输出样例
样例输入 #1
100 5
1
2
3
4
5
样例输出 #1
2
3
5
7
11
提示
【数据范围】
对于
100
%
100\%
100% 的数据,
n
=
1
0
8
n = 10^8
n=108,
1
≤
q
≤
1
0
6
1 \le q \le 10^6
1≤q≤106,保证查询的素数不大于
n
n
n。
Data by NaCly_Fish.
我的作答
#include<iostream>
using namespace std;
const int maxn = 1e8 + 10;
bool pri[maxn];
int primes[maxn], pp = 0;
int main() {
int n, p, k;
cin >> n >> p;
for (int i = 2; i <= n; i++) {
if (!pri[i])primes[++pp] = i;
for (int j = 1; primes[j] * i <= n; j++) {
pri[primes[j] * i] = 1;
if (i % primes[j] == 0)break;
}
}
while (p--) {
cin >> k;
cout << primes[k] << endl;
}
return 0;
}