【大学复健】两种质朴的素数筛法

洛谷模板P3383

判断素数

在数论相关问题中,我们经常需要使用素数。在使用之前,首先要判断该数是否为素数。考虑一个数n,根据素数定义,我们可以枚举比其小的质数直到根号n,如果n不是其中任何一个素数的倍数,则n本身为素数。同时我们可以将其记录下来便于更大数的判断。

void isprime(int n){
	int x=sqrt(n);int pd=0;
	for(int i=1;prime[i]<=x&&i<=cnt;++i){
		if(n%prime[i]==0){
			pd=1;break;
		}
	}if(!pd)prime[++cnt]=n;
}

此时发现,任何一个数,要想判断其是否为素数,我们需要先获得比其小的素数。(当然你可以直接枚举比n小的所有数,但那样显然太慢)。
我们将问题扩大:如何将1~n中的所有素数全部找出?如果能做到,那剩下的自然是合数。
比起对每一个数进行判断,更优秀的做法是反向思维,对于每个已经发现的素数,比其大的倍数显然不是素数。由此,我们引入了埃氏筛。

埃氏筛

简单来说,埃氏筛就是从小到大,将每个素数的所有倍数全部打上标记,表示其为合数。当轮到某个数而其仍然未被标记时,该数自然就是一个素数。
该算法复杂度为n lnln n。具体证明超出了我目前的知识范围。

void prime_prepare(){
	int pd[N+3]={0};
	for(int i=2;i<=N;++i){
		if(!pd[i]){
			prime[++cnt]=i;
			for(int j=1;i*j<=N;++j)
				pd[i*j]=1;
		}
	}
}

考虑如何优化这个算法。可以发现,一个合数被其所有的质数因子筛了一遍。考虑如何优化这个重复过程,我们得到了欧拉筛法。

欧拉筛

欧拉筛的复杂度为n,是极为优秀的线性筛法。那么这是如何做到的呢?
对于每个合数,我们希望它只被自己最小的质数因子筛出。如此一来,每个数至多被筛一次,复杂度即为n。
很显然,因为筛的过程是从小到大进行的,每个数必然首先被其最小的质因子筛出。所以我们的问题只剩下:如何避免一个数被其非最小的质因子筛到。
与埃氏筛不同。埃氏筛的做法,是在发现质数后,用其倍数筛。对于欧拉筛,我们考虑对于任何一个数后,从小到大枚举其质数倍进行筛查。一旦发现当前质数为当前数的约数后,退出循环。
为什么这样是对的呢?考虑一个数s,正在枚举的素数p。
如果p还不是s的约数,则二者相乘,p一定是积的最小质因子(此处可以想想)。
如果p枚举到了s的约数,此时p仍然是积的最小质因子。
但在此之后,因为先前的s含有一个更小的质因子,所以积的最小质因子不再是p。我们要舍弃掉的就是这部分。

void prime_prepare(){
	int pd[N+3]={0};
	for(int i=2;i<=N;++i){
		if(!pd[i])prime[++cnt]=i;
		for(int j=1;j<=cnt&&i*prime[j]<=N;++j){
			pd[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值