模拟的时候真没想到这是一道这么麻烦的题。。。
先来看题:
素数个数
题目描述
求1,2,cdots,N1,2,⋯,N 中素数的个数。
输入输出格式
输入格式:1 个整数N。
输出格式:1 个整数,表示素数的个数。
输入输出样例
• 对于40% 的数据,1 N 10^6
• 对于80% 的数据,1 lN 10^7
• 对于100% 的数据,1 N 10^8
当看到这道题的时候,我直接写下了逐个取余判断的代码,然而看到数据范围的时候我就慌了。。。
当时由于没学过关于素数筛选的算法,很显然除了打表我什么也干不了。。。
好吧,不废话了,直接步入正题:
由于这道题的数据范围较大,因此枚举肯定是行不通的,就算是使用正常筛法,面对10的八次方的数据也是显得十分的吃力,由此,我们要引入欧拉筛法(这里先介绍一下朴素筛法):
由于任何合数都可以拆分成若干素数的乘积,因此每当我们找到一个素数的时候,就可以将部分合数筛选出来,那么我们就解决的部分的问题。
并且讲到这里有一个隐含条件:就是没有被筛选出来的数就是素数(这个可以简化代码长度,也是理解这道题的关键),至于证明,我们可以用反证法:如果它不是素数,那么它一定是合数,而合数又可以拆分成两个素数的乘积,那么在找到它的因子的时候就一定会将其筛选出来。 证完
强忍着没有在证明的时候说显然。。。
但是,可能大家在证明的时候会发现一个问题:就是一个合数一次拆分时的素数因子可能不止一个,那么不就重复计算了吗?
因此我们要优化!!!(由此诞生了欧拉筛法)
由于下面讲解问题,我们先看代码:
1 #include<cstdio> 2 #include<cmath> 3 int prime[100000005]; 4 bool vis[100000005]; 5 int Prime(int n) 6 { 7 int ans=0; 8 for(int i=2; i<=n; i++) 9 { 10 if(!vis[i]) 11 prime[ans++]=i; 12 for(int j=0; j<ans&&i*prime[j]<=n; j++) 13 { 14 vis[i*prime[j]]=1; 15 if(i%prime[j]==0) 16 break; 17 } 18 } 19 return ans; 20 } 21 int main() 22 { 23 int n; 24 scanf("%d",&n); 25 printf("%d",Prime(n)); 26 return 0; 27 }
其实也很好理解:由于合数可以拆分成不同素数*k(k∈Z),那么当我们筛选时只要筛选出最小素数因子即可,比如12
12=4*3=6*2 很显然,我们需要的就是用素数2来筛选掉12,那么怎么实现呢?
其实这其中有一个规律:由于i是由小到大枚举的,并且数组中的素数也是由小到大枚举的,那么显然我们会先看到4*3,又因为4在和素数2筛掉8时发现竟然能被整除,并且接下来的4*3是没有意义的计算,那么每当i%素数==0是跳出即可。
如果你要问我具体该如何证明那我可以简单说一下:
由于p[j]*k(k∈Z)=i,那么a[j+1]*i=a[j]*k*a[j+1],所以a[j]乘以某个数一定在将来会把这个合数筛掉,由于a[j]比a[j+1]小,那么a[j]才可能是最小素数因子。
好的,我讲完了(好长啊qwq)