算法笔记——素数筛

素数筛是一种将 2-n 范围内的素数快速筛选出来的方法,本篇文章将讲到两种筛法,埃氏筛和欧拉筛。

介绍

素数(也叫质数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。最为朴素的判断一个数是不是素数的方法,就是遍历每一个比它小的且大于1的数,看能否被这个数整除。如果遍历结束都没有找到这样的数,那么判断这个数是一个素数。

基于这种方法,如果我们要寻找一个范围内的素数,这个做法需要对每一个数进行逐一判断,总的复杂度就会很高。即使我们能将判断单个素数时的循环次数降低到sqrt(n),这个暴力算法还是十分差劲。

埃氏筛

埃氏筛就是把素数的倍数给筛掉,会出现重复筛的情况,比如6会被2和3筛,30会被2,3,5筛

bool isPrime[10000]={0};//用来标记一个数是不是素数 ,0表示是素数,1表示不是素数 

void AiPrime (int n)
{
	isPrime[0]=isPrime[1]=1;//0和1都不是素数 
	for(int i=2;i<=n;i++){ 
		if(!isPrime[i]){//如果这个数是素数 
			for(int j=i;i*j<=n;j++){//循环条件保证不会超 
				isPrime[i*j]=1;//把素数的倍数筛掉 
			} 
		}
	}
}

一般情况下,埃氏筛够用了,其时间复杂度是O(nlognlogn)

欧拉筛

欧拉筛就是为了解决埃氏筛被重复筛的情况,说一个结论:每一个合数都有一个最小的质因子。所以,我们要保证每个合数只会被他的最小质因子筛掉,这样就可以避免很多重复的操作。为此,我们要维护一个数组,这个数组用来存取已发现的素数。

#include<iostream>
using namespace std;
bool isPrime[10000]={0};//用来标记一个数是不是素数 ,0表示是素数,1表示不是素数 
int num = 0;//用来统计素数的个数
int Prime[10000];//存素数 
//欧拉筛 
void EulerPrime(int n)
{
	isPrime[0]=isPrime[1]=1;
	for(int i=2;i<=n;i++){
		if(!isPrime[i])Prime[num++]=i; //如果这个数是素数,就存起来 
		for(int j=0;j<num;j++){
			if(i*Prime[j]>n)break;//范围超了就跳出 
			isPrime[i*Prime[j]]=1;//标记合数
			if(i%Prime[j]==0)break;//保证用最小质因子筛,避免重复标记 
		}
	}
}

来做个题:

漂亮数

思路:就是把素数筛换成漂亮数筛,只需要增加一行代码,由于要存区间内的漂亮数多少,我们再维护一个数组,表示第n个数前面有多少个漂亮数。

参考代码

#include<iostream>
using namespace std;
const int N=1e8+10;
int a[N];
int num=0; 
bool vis[N]={false};//记录当前数是不是漂亮数 
bool b[N]={false};//标记是不是素数 
int ans[N]={0};//N之前有多少个漂亮数 
void EulerPrime(int n)//线性筛选出漂亮数 
{
	b[0]=true;b[1]=true;
	for(int i=2;i<=n;i++){
		if(b[i]==false){
			a[num++]=i;
		}
		for(int j=0;j<num;++j){
			if(a[j]*i>n)break;
			b[a[j]*i]=true;
			/*就是多了下面这一行代码,
			如果i也是素数就把两个相乘的数标记成漂亮数 */
			if(!b[i])vis[a[j]*i]=true;
			if(i%a[j]==0)break;
		}
	}
	for(int i=1;i<=n;i++){//标记i之前有多少个漂亮数,可以理解成前缀和 
		ans[i]+=ans[i-1]+(vis[i]==1);
	}
}
int main()
{
	int t;
	cin>>t;
	int l,r;
	EulerPrime(1e8+10);
	while(t--){
		cin>>l>>r;
		cout<<ans[r]-ans[l-1]<<endl;
	}
}

OVER!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值