关于线性素数筛

去年听数一聚聚讲的线性筛,但是之前只记了板子,也没有对其进行过深入思考。

模板:

bool notprime[N];
int cnt,prime[N];
void init(){
	notprime[1]=1;
	for(int i=2;i<N;i++){
		if(!notprime[i]){
			prime[cnt++]=i;
		}
		for(int j=0;j<cnt&&i*prime[j]<N;j++){
			notprime[i*prime[j]]=1;
			if(i%prime[j]==0){
				break;
			}
		}
	}
}

最近发现很多人对线性筛的break有疑问——虽然此前我自己也一知半解,只记得数一聚聚强调过,加上这个才使得这个筛法是线性的——然后就略微琢磨了一下break的妙处。

当然更具体的原因是我用线性筛写的 https://blog.csdn.net/qq_38515845/article/details/82292071 这道题的题解下面有童鞋提问了,因此思索了一会儿才给出了解释。

“首先可以知道每个合数都无法避免被它最小的素因子筛一次——因为是从小到大枚举的素数,而break是为了保证每个数只被筛一次,比如i=4,prime[j]=2时如果不break,x=i*prime[j+1到cnt-1]都会在这里被筛一次,但是因为i是prime[j]的倍数,那么这些数也一定是prime[j]的倍数,因此一定会在后面被prime[j]筛一次,所以就没必要多筛了。”

这是我的回答,其实已经很详细了。

再略微展开一点点,详细一点点。

首先,我们是怎么筛掉合数的呢?假设要筛掉1到N范围内的所有合数,首先枚举的是最外层的i从2到N,如果枚举到i时发现i没有被筛掉,则说明i为素数,存到prime数组同时cnt++。然后,对于每个i,再通过从小到大枚举当前已知的素数,筛掉这些素数的i倍,假设没有break,这样显然可以筛掉指定范围内的所有合数,因为每个合数都一定可以表示为一个素因子和另一个数相乘的形式,而每个素数的所有在N范围内的倍数的数都被筛了,即所有1到N范围内的合数都被筛掉了。

没有break的缺点是很显然的——绝大部分合数都被筛了不只一遍,比如12会在i=4,prime[j]=3时筛一次,还会在i=6,prime[j]=2时被筛一次。

那么怎么才能做到使每个合数只被筛一次呢?这就是break的妙处了。

我们可以做到让合数x只被它的最小的素因子筛来实现 "线性" :当i%prime[j]==0即i是prime[j]的倍数时,i乘以任何大于prime[j]的素数都会是prime[j]的倍数,因此我们可以保证它们会在之后被它们最小的素因子prime[j]给筛掉。(这里纠正一下我的原评论,被最小的素因子筛说是保证只被筛一次的手段应该更加确切)

线性筛还是很好用的,积性函数、打素数表什么的常用到它。

猜你喜欢

转载自blog.csdn.net/qq_38515845/article/details/82322774