普通质数筛法


前言

从暴力的直接循环开始,一步步对算法进行优化,一步步简化时间复杂度,并尽可能讨论各种筛法的优劣,以及其中每一步的意义。


一、暴力循环

1.优点

思路直接,容易被像我们这样的新手第一时间想到,一个质数,除了1和它本身以外不再有其他因数,那么想知道一个数是不是质数,干脆直接去看看它有几个因子

2.小技巧

对于大于4的素数,总是可以用6x+1或6x-1来表示
讨论:任意一个自然数都可以用
6x、6x+1、6x+2、6x+3、6x+4、6x+5中的某个来表示
而6x、6x+2、6x+3、6x+4都存在直观可见的因子,故不会为素数。

3.关键代码

仅仅提供思路,理解后能灵活运用即可
代码如下:

#include<iostream>
#include<math.h>
using namespace std;
int main(){
	int n,m=0;
	cin>>n;
		for(int j=2;j<n;j++){/*
		也可将n改为pow(n,0.5),取平方根,能够进一步简化运算过程
		不过这样的话每次都要调用一下函数,比较慢;
		也有的会写为i*i<=n,不过存在溢出风险,使其变为负数,影响结果判断;
		最好写为i<=n/i,不存在溢出风险*/
			if(n%j==0){
				m=1;break;
			}
		}
		(m==1)?cout<<"NO":cout<<"YES";
	} 

4.缺点

显而易见,当n取值过大时,一次次的判断将极大地影响工作效率,特别是在解决一些有时间要求的算法题时,这样的思路很容易引发超时。


二、埃氏筛

1.改进

任一合数仅能被唯一分解成有限个素数的乘积,意味通过对一个已知质数的处理可以选择出其他与之相关的合数,比如2为质数,通过对2的倍数处理,可以选出4,6,8等以2为因子的合数。
时间复杂度为O(n* log log n)

2.关键代码

代码如下:

#include<iostream>
#include<math.h>
using namespace std;
int a[100005];
int vis[100005];
void f(int n){
	int cnt=0;
	for(int i=2;i<=n;i++){
		if(!vis[i]){//进行判断,若为已被标记的合数则会直接跳过
			a[cnt++]=i;
			for(int j=2;j*a[cnt-1]<=n;j++){
				vis[j*a[cnt-1]]=1;//对范围内该质数的倍数进行标记
			}
		}		
	}
}
int main(){
	int n;
	cin>>n;
	f(n);
	for(int i=0;a[i]!=0;i++){
		cout<<a[i]<<" ";
	}
}

3.缺点

以12为例,在整个标记过程中,在处理2的倍数时,被标记过了一次,然后在处理3的倍数时,又被重新标记了一次,若数据较小,则影响不大,但在提交答案时,系统会测试一些处于边界条件下的数据,一次次的重复标记同样会影响效率,可能会导致超时。


三.欧拉筛法

1.改进

埃氏筛存在重复标记的问题,那么想解决这个问题,就需要对每次对合数的标记进行一个规范,从而达到一个合数将有且只有一种标记方式,我们想到的方法是将一个合数化为其最小质因数与另一个数之积。

时间复杂度为O(n)

2.关键代码

#include<iostream>
using namespace std;
int a[100005];
int cnt=0;   
int p[100005];
void f(int n){
    for(int i=2;i<=n;i++){
        if(!p[i])a[cnt++]=i;
        for(int j=0;j<cnt&&i*a[j]<=n;j++){            
            p[i*a[j]]=1;
            /*对合数进行标记,并且是依据最小质因数来进
            行标记,可以使用反证法来说明:
            若此时的质数a[j]不是最小质因数,令m=i*a[j],那么必定存在
            另一个数为m的最小质因数,令该数为x,那么存在m=x*y,并且
            存在x<a[j],y>i的关系,然而在整个过程中,i一直是从小到
            大依次使用的,不可能突然出现一个比i大的y*/
            if(i%a[j]==0)break;
            /*最关键的一步,用来避免重复标记,可以通过假设法来理解:
            此时i恰为a[j]的倍数,不妨令i=k*a[j],然后循环继续进行,
            在下一步,令m=i*a[j+1],m同样等于k*a[j]*a[j+1],此时再
            看,对于m来说,它的最小质因数并非a[j+1],仍是a[j],意味
            着发生了重复标记,违背了我们一开始的想法*/
        }
    }
}
int main(){
	int n;
	cin>>n;
	f(n);
	for(int i=0;a[i]!=0;i++){
		cout<<a[i]<<" ";
	}
}

4.质数距离

Prime Distance

1.分析

1.左右端点的范围很大,但两点间距却不大,所以我们不必开一个那么大的数组,只需要开一个大小为1000 005的数组就够了,过程中把结果处理一下就可以了
2.判断一个数是否为素数,我们知道任何一个自然数都可以唯一分解为质因数之积,那么若为约数,必定存在一个处于2到 根号n的质因数,即便原来的数据很大,我们也只需要看它在这一范围内是否存在质因数,那么我们也就缩减了数据
3。左端点小于2时化为2,不然后面不好确定筛选质因数大于左端点的第一个位置
4.因为存在乘法,过程中可能会出现爆int的情况,所以直接使用long long 吧

.此处为多组输入!!!

2.代码

#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
ll l,r;
ll prim[1000005];
ll prim2[1000005];
ll a[1000005];
ll cnt;
ll ma,mi;
void gp(int n){
	for(int i=2;i<=n;i++){
		if(!a[i])prim[cnt++]=i;
		for(int j=0;j<cnt&&prim[j]<=n/i;j++){
			a[i*prim[j]]=1;
			if(i%prim[j]==0)break;
		}
	}
}
int main(){
	gp(1000001);
	while(cin>>l>>r){
		if(l<=1)l=2;
		memset(a,0,sizeof(a));
		memset(prim2,0,sizeof(prim2));
		for(int i=0;;i++){
			if(prim[i]*prim[i]>r)break;
			ll k;
			k=l/prim[i]+(l%prim[i]>0);
			if(k==1)k=2;
			for(int j=k;j*prim[i]<=r;j++)
				if(j*prim[i]>=l)a[j*prim[i]-l]=1;		
		}
	cnt=0;
	for(int i=0;i<=r-l;i++){
		if(a[i]==0)prim2[cnt++]=l+i;
	}
	mi=ma=1;
	for(int i=1;i<cnt;i++){
		int k=prim2[i]-prim2[i-1];
		if(k<prim2[mi]-prim2[mi-1])mi=i;
		if(k>prim2[ma]-prim2[ma-1])ma=i;
	}
	if(cnt<2)cout<<"There are no adjacent primes."<<endl;
	else cout<<prim2[mi-1]<<","<<prim2[mi]<<" are closest, "<<prim2[ma-1]<<","<<prim2[ma]<<" are most distant."<<endl;
	}
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值