【数论基础】素数与素数筛
质数是啥就略了,这里讲两个找出N范围内的质数的算法。
1.Erotothenes算法
(以下简称埃筛)
筛法思路:给出2—N是自然数表,从前往后筛掉合数,剩下的就是素数。步骤如下:
①首先找到2为素数,则2的倍数一定是合数,筛掉;
②2后面第一个没有筛掉的数一定是素数,即3,同样筛掉3的倍数,以此类推,直到筛掉所有合数为止。
时间复杂度是O(nloglogn),具体可以手推,数据较小可以近似看作O(n),但实际比O(n)大一点
inline void Erotothenes(){ for(int i=2;i<=n;i++) if(flag[i]){ pri[++cnt]=i; for(int j=2*i;j<=n;j+=i) flag[j]=false; } }
2.Euclid算法
(以下简称欧筛)
筛法思路:手推后会发现,埃筛会多次筛到一些合数,比如6会被2,3筛到
那么欧筛就是不处理多余的筛选,比如6就不会被3筛到
在埃筛思路基础上,会多进行一次判断
inline void make_prime_list(){ for(int i=2;i<=n;i++){ if(!flag[i]) pri[++cnt]=i; for(int j=1;j<=cnt && pri[j]<=n/i;j++){ flag[i*pri[j]]=true; if(i%pri[j]==0) break;//保证合数总被最小因子筛掉 } } }
对于那一行的判断,可以这么思考:
如果不考虑那个判断,循环继续,pri[j]就会大于i的最小质因子,那么这里筛到的数会被后面更大的数再筛一遍
即比一个合数的最小质因子大的质数和该合数的乘积可用一个更大的合数和比其小的质数相乘得到
(这个题我的埃筛被卡掉了?)放欧筛的模板
#include<cstdio> #include<iostream> #include<cmath> #include<cctype> using namespace std; inline int read(){ int s=0;bool flag=true;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')flag=false;ch=getchar();} while(isdigit(ch)){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();} return flag?s:-s; } inline void out_put(int x){ if(x<0) x=-x,putchar('-'); if(x>9) out_put(x/10); putchar(x%10+'0'); } inline void print(int x){out_put(x),puts("");} const int N=1e8+10; int n,m,cnt; int pri[N]; bool flag[N]; inline void make_prime_list(){ for(int i=2;i<=n;i++){ if(!flag[i]) pri[++cnt]=i; for(int j=1;j<=cnt && pri[j]<=n/i;j++){ flag[i*pri[j]]=true; if(i%pri[j]==0) break;//保证合数总被最小因子筛掉 } } } signed main(){ n=read(),m=read(); make_prime_list(); for(int i=1;i<=m;i++) print(pri[read()]); return 0; }
也是模板,打欧筛就行
这是一道范围令人呕吐的题,单打欧筛是没用的,但是我们可以打50000范围内的素数,再在[l,r]的区间里筛掉合数,最后统计素数个数
//仔细观察后发现R-L只有1000000。又R最大到maxlongint, //也就是说L..R上的合数的最小质因子不会大于50000, //否则这个数的乘方就会比maxlongint还要大了。 //先把50000以内的素数求出来, //然后用这些素数将L到R的合数筛掉即可。 //复杂度是O(50000log(50000)+prime*log(r-l))。 //可以在时限内出解 #include<cstdio> #include<iostream> #include<cmath> #include<cctype> #define int long long using namespace std; inline int read(){ int s=0;bool flag=true;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')flag=false;ch=getchar();} while(isdigit(ch)){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();} return flag?s:-s; } inline void out_put(int x){ if(x<0) x=-x,putchar('-'); if(x>9) out_put(x/10); putchar(x%10+'0'); } inline void print(int x){out_put(x),puts("");} const int N=1e6+10; int n,m,cnt,ans; int pri[N]; bool flag[N],vis[N]; inline void make_prime_list(){ for(int i=2;i<=N;i++){ if(!flag[i]) pri[++cnt]=i; for(int j=1;j<=cnt && pri[j]<=N/i;j++){ flag[i*pri[j]]=true; if(i%pri[j]==0) break; } } } inline void prime_griddle(){ for(int i=1;i<=cnt;i++){ int p=pri[i],start=(m+p-1)/p*p>2*p?(m+p-1)/p*p:2*p; //我们从大于L的最小的能被p整除的数开始,(l+p-1)就等于ceil(l+p-1),因为有可能会在接下来筛的过程中把自己也一起筛掉,所以在此特判一下 for(int j=start;j<=n;j+=p) vis[j-m+1]=true;//因为如果从j开始标记的话可能会爆vis的空间,所以我们这里从1开始标记合数,原理在上面已经叙述过了 } for(int i=1;i<=n-m+1;i++) if(!vis[i]) ans++; } signed main(){ m=read(),n=read(); make_prime_list(); prime_griddle(); print(ans); return 0; }