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 }
设一合数 C(要筛掉)的最小质因数是 p1,令 B=C/p1(C=B×p1),则 B 的最小质因数不小于 p1(否则 C 也有这个更小因子)。那么当外层枚举到 i=B 时,我们将会从小到大枚举各个质数;因为 i=B 的最小质因数不小于 p1,所以 i 在质数枚举至 p1 之前一定不会break,这回,C 一定会被 B×pi 删去。
核心:亲爱的 B 的最小质因数必不小于 p1。
例:315=3×3×5×7,其最小质因数是 3。考虑 i=315/3=105 时,我们从小到大逐个枚举质数,正是因为 i 的最小质因数也不会小于 3(本例中就是 3),所以当枚举 j=1(Prime[j]=2) 时,i 不包含 2 这个因子,也就不会break,直到 Prime[j]=3 之后才退出。
当然质数不能表示成“大于1的某数×质数”,所以整个流程中不会标记。