三种素数筛

P3383 【模板】线性筛素数

来源:洛谷 https://www.luogu.com.cn/problem/P3383

 这题考 [素数筛] ,但数据范围很毒瘤,100%数据n=10^8,意味着只有 [线性筛] 才能AC

以下提供3种素数筛法

①朴素算法(时间复杂度O(n*sqrt(n))

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int read(){
 4     int flag=0,x=0;
 5     char a=getchar();
 6     while(a<'0'||a>'9'){
 7         if(a=='-')flag=1;
 8         a=getchar();
 9     }
10     while(a>='0'&&a<='9'){
11         x=x*10+a-'0';
12         a=getchar();
13     }
14     return flag?-x:x;
15 }
16 void write(int x){
17     if(x<0){
18         putchar('-');
19         x=-x;
20     }
21     if(x>9){
22         write(x/10);
23     }
24     putchar(x%10+'0');
25 }
26 bool pd(int x){
27     for(int i=2;i<=sqrt(x);i++){
28         if(x%i==0)return 0;
29     }
30     return 1;
31 }
32 int ans[100000005],k; 
33 int main(){
34     int n=read();
35     int m=read();
36     for(int i=2;i<=n;i++){
37         if(pd(i))ans[++k]=i;
38     }
39 //    for(int i=1;i<=k;i++){
40 //        write(ans[i]);
41 //        putchar(' ');
42 //    }
43     for(int i=1;i<=k;i++){
44         int a=read();
45         write(ans[a]);
46         puts(" ");
47     }
48     return 0;
49 }

在这道题中显然时间复杂度是 O(10^12)

垃圾代码,砸掉!

 

 

 

②艾氏筛(时间复杂度O(n log log(n))

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int ss[100000005],ans[100000005],k;
 4 int read(){
 5     int flag=0,x=0;
 6     char a=getchar();
 7     while(a<'0'||a>'9'){
 8         if(a=='-')flag=1;
 9         a=getchar();
10     }
11     while(a>='0'&&a<='9'){
12         x=x*10+a-'0';
13         a=getchar();
14     }
15     return flag?-x:x;
16 }
17 void write(int x){
18     if(x<0){
19         putchar('-');
20         x=-x;
21     }
22     if(x>9){
23         write(x/10);
24     }
25     putchar(x%10+'0');
26 }
27 int main(){
28     int n=read();
29     int m=read();
30     for(int i=2;i<=n;i++){
31         if(!ss[i]){
32             ans[++k]=i;
33             for(int j=i;i*j<=n;j++){
34                 ss[i*j]=1;
35             }
36         }    
37     }
38 //    for(int i=1;i<=k;i++){
39 //        write(ans[i]);
40 //        putchar(' ');
41 //    }
42     for(int i=1;i<=m;i++)
43     {
44         int a=read();
45         write(ans[a]);
46         puts(" ");
47     }
48     return 0;
49 }

O(n log log(n))已经是很优秀的运算速度了!

但很可惜,仍然有重复运算的情况出现占时间

例如:12;

在i=2,j=6时已经被标记过了;

但在i=3,j=4时又被标记了一次

24就更不用说了(24:你礼貌吗

 

而且!

这种算法交上去出现了玄学错误

 

 

 ③欧拉筛(时间复杂度O(n))

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int ss[100000005],ans[100000005],k;
 4 int read(){
 5     int flag=0,x=0;
 6     char a=getchar();
 7     while(a<'0'||a>'9'){
 8         if(a=='-')flag=1;
 9         a=getchar();
10     }
11     while(a>='0'&&a<='9'){
12         x=x*10+a-'0';
13         a=getchar();
14     }
15     return flag?-x:x;
16 }
17 void write(int x){
18     if(x<0){
19         putchar('-');
20         x=-x;
21     }
22     if(x>9){
23         write(x/10);
24     }
25     putchar(x%10+'0');
26 }
27 int main(){
28     int n=read();
29     int m=read();
30     ss[1]=1;
31     for(int i=2;i<=n;i++){
32         if(!ss[i]){
33             ans[++k]=i;
34         }
35         for(int j=1;j<=k&&i*ans[j]<=n;j++){
36             ss[i*ans[j]]=1;
37         }
38     }
39 //    for(int i=1;i<=k;i++){
40 //        write(ans[i]);
41 //        putchar(' ');
42 //    }
43     for(int i=1;i<=m;i++)
44     {
45         int a=read();
46         write(ans[a]);
47         puts(" ");
48     }
49     return 0;
50 }

时间复杂度是极为优秀的O(n),因此也叫~~~线性筛~~~!!!

但其实------

你们若是把上边的代码copy上去是会-------

 

 

 TLE的!

为什么呢?

因为它并不是一个有灵魂的欧拉筛代码

缺少了break偷懒的欧拉筛不是一个好的欧拉筛

欧拉筛的核心代码:

if(i%ans[j]==0)break;

一行代码学问大了去了

首先

原理

其中的Prime数组就是上边代码的ans了

 

 

 

 

正确性(所有合数都会被标记)证明

 

 

线性复杂度证明

 

 题解原地址:https://www.luogu.com.cn/problem/solution/P3383

就是置顶的那篇

 

最后

完整AC代码:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 int ss[100000005],ans[100000005],k;
 4 int read(){
 5     int flag=0,x=0;
 6     char a=getchar();
 7     while(a<'0'||a>'9'){
 8         if(a=='-')flag=1;
 9         a=getchar();
10     }
11     while(a>='0'&&a<='9'){
12         x=x*10+a-'0';
13         a=getchar();
14     }
15     return flag?-x:x;
16 }
17 void write(int x){
18     if(x<0){
19         putchar('-');
20         x=-x;
21     }
22     if(x>9){
23         write(x/10);
24     }
25     putchar(x%10+'0');
26 }
27 int main(){
28     int n=read();
29     int m=read();
30     ss[1]=1;
31     for(int i=2;i<=n;i++){
32         if(!ss[i]){
33             ans[++k]=i;
34         }
35         for(int j=1;j<=k&&i*ans[j]<=n;j++){
36             ss[i*ans[j]]=1;
37             if(i%ans[j]==0)break;
38         }
39     }
40 //    for(int i=1;i<=k;i++){
41 //        write(ans[i]);
42 //        putchar(' ');
43 //    }
44     for(int i=1;i<=m;i++)
45     {
46         int a=read();
47         write(ans[a]);
48         puts(" ");
49     }
50     return 0;
51 }

 

 

设一合数 CC(要筛掉)的最小质因数是 p_1p1,令 B = C / p_1B=C/p1C = B × p_1C=B×p1),则 BB 的最小质因数不小于 p_1p1(否则 CC 也有这个更小因子)。那么当外层枚举到 i = Bi=B 时,我们将会从小到大枚举各个质数;因为 i = Bi=B 的最小质因数不小于 p_1p1,所以 ii 在质数枚举至 p_1p1 之前一定不会break,这回CC 一定会被 B × p_iB×pi 删去。

核心:亲爱的 BB 的最小质因数必不小于 p_1p1

例:315 = 3 × 3 × 5 × 7315=3×3×5×7,其最小质因数是 33。考虑 i = 315 / 3 = 105i=315/3=105 时,我们从小到大逐个枚举质数,正是因为 ii 的最小质因数不会小于 33(本例中就是 33),所以当枚举 j = 1 (Prime[j] = 2)j=1(Prime[j]=2) 时,ii 不包含 22 这个因子,也就不会break,直到 Prime[j] = 3Prime[j]=3 之后才退出。

当然质数不能表示成“大于1的某数×质数”,所以整个流程中不会标记。

posted @ 2021-08-19 23:28  九州霜  阅读(91)  评论(1编辑  收藏  举报