埃氏筛法与欧拉筛(超级详解)
Posted 大佬,菜菜,带带
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了埃氏筛法与欧拉筛(超级详解)相关的知识,希望对你有一定的参考价值。
素数的定义:
首先我们明白:素数的定义是只能整除1和本身(1不是素数)。
我们判断一个数n是不是素数时,可以采用试除法,即从i=2开始,一直让n去%i,直到i到了n-1.
代码如下:
#include<stdio.h>
signed main()
int n;
for (int i = 2; i <= n - 1; i++)
if (n % i == 0)
printf("%d 不是素数",n);
return 0;
printf("%d 是素数", n);
但是我们仔细想一下,i是完全没有必要到n-1的,如果到了n/2都除不尽,那么后面就没有必要继续进行试除了。但是你再想想,其实只需要到sqrt(n)就可以了。太细节了
于是,时间复杂度简化:
#include<stdio.h>
signed main()
int n;
for (int i = 2; i*i <= n; i++)//等价于i<=sqrt(n),在这里写成i*i<=n是乘法比开根号要快。
//这里之所以是<=n而不是<=n-1,可以参考对于9的素数判断。
if (n % i == 0)
printf("%d 不是素数",n);
return 0;
printf("%d 是素数", n);
但是问题来了,如果一两个数让你去判断,你这么试除一下还行,那要是一堆大的离谱且多的荒谬的数据让你去判断,就算你sqrt,你需要循环的次数也是一个天文数字。这个时候,我们就可以通过一些算法来实现对于大数据(大且多)素数的判断。
埃筛与欧拉筛的实质:
其实埃筛与欧拉筛的实质都且就是围绕这一句话:素数的倍数不是素数。
比如说让你输出100000——1e5内所有的素数
那我们就筛就好啦,首先咱需要创建一个存素数的数组和一个bool类型的数组(用来判断该元素是否是素数,或者int 类型的数组,因为我们只需要存0或1.但是如果数据实在太大,我们可以vector<int>。这样子就爆不了哈哈。我们也需要注意是否需要开long long 哦。
埃氏筛法:
//埃氏筛法
int n=1e5;
bool shai[n];
//vector<int >shai;
int cun[n];
signed main()
int cnt = 0;
//如果你用的是vector,则进行注释部分操作
//shai.push_back(1), shai.push_back(1);//在0,1两个位置先添加。0,1都不是素数所以都添1。
//for (int i = 2; i <= n; i++)
//
// shai.push_back(0);
//
for (int i = 2; i <= n; i++)
if (!shai[i])//如果没有被标记
cun[cnt++] = i;
for (int j = 2; j <= n; j++)
if (i * j > n)break;//超过数据大小就退掉。
shai[i * j] = 1;//1标记的都是素数的倍数——所以不是素数。
for (int i = 0; i < cnt; i++)
printf("%d ", cun[i]);
我们先看一看欧拉筛
欧拉筛:
#include<iostream>
using namespace std;
bool a[100001]=1,1;//同上问一样i=0,i=1的时候都不是质数 ,所以直接标记,数组大小你看着设定,同上文一样太大考虑vector
int b[100001];//存质数
long long n;
int main()
int cnt=0;
cin>>n;//看你要查的范围是多大啦。
for(int i=2;i<=n;i++)
if (a[i]==0) b[cnt++]=i;
for(int j=1;j<=cnt;j++)
if(i*b[j]>n)break;// 如果超出给出的范围,那么就退出循环
a[i*b[j]]=1;//素数的倍数不是素数,进行标记。
if(i%b[j]==0)break;//超级关键的只标记一次
for (int i = 0; i < cnt; i++)
printf("%d ", cun[i]);
为什么欧拉筛比埃筛要好:
欧拉筛比埃筛要快很多很多倍(大数据的时候10倍是有的)。记得之前做题用埃筛提交1600ms起步,用欧拉筛96ms。
那么为什么呢?
我们看看埃筛,就从2开始,它不是素数,所以内循环会标记4,6,8,10,12······一直到退出循环,然后当外层循环到3的时候,它又会标记6,9,12······,在这里我们就能看出一点问题,有数被重新标记了,而且循环到后面重复标记的数量一定不少。这不就浪费时间了嘛。然后它就进阶了~~~。
我们看看欧拉筛,欧拉筛内部有一个很重要的判断语句:if(i%b[j]==0)break; 就是这一个小步骤取消了多余的重复标记。
打个表给你看一下(感谢:平凡@之路):
i= 2//当i取2时
j =1 b[1] = 2 i*b[j] = 4
i =3//当i取3时
j=1 b[1] = 2 i*b[j] =6
j=2 b[2] = 3 i*b[j] = 9
i=4//当i取4时
j=1 b[1] = 2 i*b[j] = 8//上面提到为什么退出循环,因为如果不退出循环,就会标记一次12,所以不行
i=5//当i取5时
j=1 b[1] = 2 i*b[j] = 10
j=2 b[2] = 3 i*b[j ]=15
j=3 b[3] = 5 i*b[j] = 25
i=6//当i取6时
j=1 b[1] = 2 i*b[j] =12//这里已经标记了12了。
i=7//当i取7时
j=1 b[1] = 2 i*b[j] = 14
j=2 b[1] = 3 i*b[j] = 21
j=3 b[1] = 5 i*b[j] = 35
j=4 b[1] = 7 i*b[j] = 49
这个自己琢磨一下也能明白个大概吧。
但是我们回过头再想一下欧拉筛,你会发现当你仅仅只需要判断是否是素数而不需要输出的时候。埃筛直接标记即可,但是欧拉筛的使用由于其特定的循环势必会多出一个数组。这就是空间换时间啦!
在这里提供一个题目:
传送门:Problem - 230B - Codeforces
题目就是给出数判断是否只含有三个因子,是就输出YES,否则输出NO。
我们不难发现,如果存在这种数n,那么势必有int k=sqrt(n),k*k=n。然后我们去判断n是否是素数即可。
#include <iostream>
#include <cmath>
using namespace std;
int a[1000100],b[1000010];
void isans()
int cnt = 0;
for (int i = 1; i <= 1000000; i++) a[i] = 1;
a[1] = 0;
for (int i = 2; i <= 1000000; i++)
if(a[i]==1)b[++cnt] = i;
for (int j = 1; j <= cnt; j++)
if (i * b[j] > 1000000)break;
a[i * b[j]] = 0;
if (!(i % b[j]))break;
int main()
std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n;
cin >> n;
isans();
while (n--)
long long x;
cin >> x;
long long num = sqrt(x);
if (num * num == x && a[num])
cout << "YES\\n";
else
cout << "NO\\n";
return 0;
欧拉筛的时间与空间:
埃氏筛法的时间与空间:
以上是关于埃氏筛法与欧拉筛(超级详解)的主要内容,如果未能解决你的问题,请参考以下文章