当前位置:   article > 正文

埃氏筛法与欧拉筛(超级详解)

欧拉筛

素数的定义:

首先我们明白:素数的定义是只能整除1和本身(1不是素数)。

我们判断一个数n是不是素数时,可以采用试除法,即从i=2开始,一直让n去%i,直到i到了n-1.

代码如下:

  1. #include<stdio.h>
  2. signed main()
  3. {
  4. int n;
  5. for (int i = 2; i <= n - 1; i++)
  6. {
  7. if (n % i == 0)
  8. {
  9. printf("%d 不是素数",n);
  10. return 0;
  11. }
  12. }
  13. printf("%d 是素数", n);
  14. }
'
运行

但是我们仔细想一下,i是完全没有必要到n-1的,如果到了n/2都除不尽,那么后面就没有必要继续进行试除了。但是你再想想,其实只需要到sqrt(n)就可以了。太细节了

于是,时间复杂度简化:

  1. #include<stdio.h>
  2. signed main()
  3. {
  4. int n;
  5. for (int i = 2; i*i <= n; i++)//等价于i<=sqrt(n),在这里写成i*i<=n是乘法比开根号要快。
  6. {//这里之所以是<=n而不是<=n-1,可以参考对于9的素数判断。
  7. if (n % i == 0)
  8. {
  9. printf("%d 不是素数",n);
  10. return 0;
  11. }
  12. }
  13. printf("%d 是素数", n);
  14. }
'
运行

但是问题来了,如果一两个数让你去判断,你这么试除一下还行,那要是一堆大的离谱且多的荒谬的数据让你去判断,就算你sqrt,你需要循环的次数也是一个天文数字。这个时候,我们就可以通过一些算法来实现对于大数据(大且多)素数的判断

埃筛与欧拉筛的实质:

其实埃筛与欧拉筛的实质都且就是围绕这一句话:素数的倍数不是素数。

比如说让你输出100000——1e5内所有的素数

那我们就筛就好啦,首先咱需要创建一个存素数的数组和一个bool类型的数组(用来判断该元素是否是素数,或者int 类型的数组,因为我们只需要存0或1.但是如果数据实在太大,我们可以vector<int>。这样子就爆不了哈哈。我们也需要注意是否需要开long long 哦。

埃氏筛法:

  1. //埃氏筛法
  2. int n=1e5;
  3. bool shai[n];
  4. //vector<int >shai;
  5. int cun[n];
  6. signed main()
  7. {
  8. int cnt = 0;
  9. //如果你用的是vector,则进行注释部分操作
  10. //shai.push_back(1), shai.push_back(1);//在0,1两个位置先添加。0,1都不是素数所以都添1。
  11. //for (int i = 2; i <= n; i++)
  12. //{
  13. // shai.push_back(0);
  14. //}
  15. for (int i = 2; i <= n; i++)
  16. {
  17. if (!shai[i])//如果没有被标记
  18. {
  19. cun[cnt++] = i;
  20. for (int j = 2; j <= n; j++)
  21. {
  22. if (i * j > n)break;//超过数据大小就退掉。
  23. shai[i * j] = 1;//1标记的都是素数的倍数——所以不是素数。
  24. }
  25. }
  26. }
  27. for (int i = 0; i < cnt; i++)
  28. {
  29. printf("%d ", cun[i]);
  30. }
  31. }

我们先看一看欧拉筛

欧拉筛:

  1. #include<iostream>
  2. using namespace std;
  3. bool a[100001] = { 1,1 };//同上问一样i=0,i=1的时候都不是质数 ,所以直接标记,数组大小你看着设定,同上文一样太大考虑vector
  4. int b[100001];//存质数
  5. long long n;
  6. int main()
  7. {
  8. int cnt = 0;
  9. cin >> n;//看你要查的范围是多大啦。
  10. for (int i = 2; i <= n; i++)
  11. {
  12. if (a[i] == 0) b[++cnt] = i;
  13. for (int j = 1; j <= cnt; j++)
  14. {
  15. if (i * b[j] > n)break;// 如果超出给出的范围,那么就退出循环
  16. a[i * b[j]] = 1;//素数的倍数不是素数,进行标记。
  17. if (i % b[j] == 0)break;//超级关键的只标记一次
  18. }
  19. }
  20. for (int i = 1; i <= cnt; i++)
  21. {
  22. printf("%d ", b[i]);
  23. }
  24. }

Emmm,感谢JIAN LAI指正。该文已经更新。

<bitset>

update by 2023.7.20.

很早就想更新博客了,但是一直在为了今年acm而刷题。话不多说。

上文提到在n(素数筛范围过大时)使用vector,可行是可行,但是没有必要。因为vector的操作以及所占内存来看是性价比不高的。一般情况,当所给题目n>1e7时候,就转不动了。我们在欧拉筛之所以要引入另一个数组是用于检测该数是否为素数,从而便于判断,如上文的a数组。我们定义为bool类型。在这里引入另外一个类型,bitset,类似于Bool,每一个位置存储一个0或1,由于bool类型是int类型的typedef,因此需要占用4bit的内存空间但是bitset每一个位置所占内存为1bit,更优。

这是其使用方法:

  1. #include<iostream>
  2. #include<bitset>//需要引入头文件
  3. using namespace std;
  4. bitset<1000001>a;//定义方式,这里就相当于定义bool a[1000001];
  5. int b[100001];//存质数
  6. long long n;
  7. int main()
  8. {
  9. int cnt = 0;
  10. cin >> n;//看你要查的范围是多大啦。
  11. for (int i = 2; i <= n; i++)
  12. {
  13. if (a[i] == 0) b[++cnt] = i;
  14. for (int j = 1; j <= cnt; j++)
  15. {
  16. if (i * b[j] > n)break;// 如果超出给出的范围,那么就退出循环
  17. a[i * b[j]] = 1;//素数的倍数不是素数,进行标记。
  18. if (i % b[j] == 0)break;//超级关键的只标记一次
  19. }
  20. }
  21. for (int i = 1; i <= cnt; i++)
  22. {
  23. printf("%d ", b[i]);
  24. }
  25. }

为什么欧拉筛比埃筛要好:

欧拉筛比埃筛要快很多很多倍(大数据的时候10倍是有的)。记得之前做题用埃筛提交1600ms起步,用欧拉筛96ms。

那么为什么呢?

我们看看埃筛,就从2开始,它不是素数,所以内循环会标记4,6,8,10,12······一直到退出循环,然后当外层循环到3的时候,它又会标记6,9,12······,在这里我们就能看出一点问题,有数被重新标记了,而且循环到后面重复标记的数量一定不少。这不就浪费时间了嘛。然后它就进阶了~~~。

我们看看欧拉筛,欧拉筛内部有一个很重要的判断语句:if(i%b[j]==0)break; 就是这一个小步骤取消了多余的重复标记。

打个表给你看一下(感谢:平凡@之路):

  1. i= 2//当i取2时
  2. j =1 b[1] = 2 i*b[j] = 4
  3. i =3//当i取3时
  4. j=1 b[1] = 2 i*b[j] =6
  5. j=2 b[2] = 3 i*b[j] = 9
  6. i=4//当i取4时
  7. j=1 b[1] = 2 i*b[j] = 8//上面提到为什么退出循环,因为如果不退出循环,就会标记一次12,所以不行
  8. i=5//当i取5时
  9. j=1 b[1] = 2 i*b[j] = 10
  10. j=2 b[2] = 3 i*b[j ]=15
  11. j=3 b[3] = 5 i*b[j] = 25
  12. i=6//当i取6时
  13. j=1 b[1] = 2 i*b[j] =12//这里已经标记了12了。
  14. i=7//当i取7时
  15. j=1 b[1] = 2 i*b[j] = 14
  16. j=2 b[1] = 3 i*b[j] = 21
  17. j=3 b[1] = 5 i*b[j] = 35
  18. j=4 b[1] = 7 i*b[j] = 49

这个自己琢磨一下也能明白个大概吧。

但是我们回过头再想一下欧拉筛,你会发现当你仅仅只需要判断是否是素数而不需要输出的时候。埃筛直接标记即可,但是欧拉筛的使用由于其特定的循环势必会多出一个数组。这就是空间换时间啦!

在这里提供一个题目:

传送门:Problem - 230B - Codeforces

题目就是给出数判断是否只含有三个因子,是就输出YES,否则输出NO。

我们不难发现,如果存在这种数n,那么势必有int k=sqrt(n),k*k=n。然后我们去判断n是否是素数即可。

  1. #include <iostream>
  2. #include <cmath>
  3. using namespace std;
  4. int a[1000100],b[1000010];
  5. void isans()
  6. {
  7. int cnt = 0;
  8. for (int i = 1; i <= 1000000; i++) a[i] = 1;
  9. a[1] = 0;
  10. for (int i = 2; i <= 1000000; i++)
  11. {
  12. if(a[i]==1)b[++cnt] = i;
  13. for (int j = 1; j <= cnt; j++)
  14. {
  15. if (i * b[j] > 1000000)break;
  16. a[i * b[j]] = 0;
  17. if (!(i % b[j]))break;
  18. }
  19. }
  20. }
  21. int main() {
  22. std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  23. int n;
  24. cin >> n;
  25. isans();
  26. while (n--)
  27. {
  28. long long x;
  29. cin >> x;
  30. long long num = sqrt(x);
  31. if (num * num == x && a[num])
  32. cout << "YES\n";
  33. else
  34. cout << "NO\n";
  35. }
  36. return 0;
  37. }

欧拉筛的时间与空间:

埃氏筛法的时间与空间:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/人工智能uu/article/detail/947523
推荐阅读
相关标签
  

闽ICP备14008679号