[总结] 筛法

埃筛

  • 考虑这样一件事情:如果 \(x\) 是合数,那么 \(x\) 的倍数也一定是合数。利用这个结论,我们可以避免很多次不必要的检测。

  • 如果我们从小到大考虑每个数,然后同时把当前这个数的所有(比自己大的)倍数记为合数,那么运行结束的时候没有被标记的数就是素数了。

代码实现

  • 先把所有的数都看成素数,然后从 \(2\) 开始筛去倍数就可以了
int Eratosthenes(int n){
	for(int i=0;i<=n;i++)isp[i]=true;
	isp[1]=isp[0]=false;
	for(int i=2;i<=n;i++){
		if(isp[i]){
			primes[++cnt]=i;
			for(int j=i*i;j<=n;j+=i){//可以保证i*i之前的已经筛完了 
				isp[j]=false;
			}
		}
	}
}
  • 复杂度是 \(O(nloglogn)\)

  • \(i* i\) 开始筛可以显著优化,但不会改变复杂度


线性筛

  • 与埃筛不同的是,保证每个合数都在它的最小因数筛掉它

代码实现

  • 可以枚举当前 \(primes\) 数组里存储的质数,然后结合 \(i\) 筛去 \(i* primes[j]\)

考虑这么一个问题:

在结合的时候如果 $i \ mod \ primes[j]=0 $,那么说明 $i* primes[j] $ 这个数一定被 \(primes[j]\) 约去了,再枚举 \(primes\) 的元素就不行了,直接break就可以

void Segment(int n){
	notp[1]=true;
	for(int i=2;i<=n;i++){
		if(!notp[i])primes[++cnt]=i;
		for(int j=1;j<=cnt;j++){
			int tmp=primes[j]*i;
			if(tmp>n)break;
			notp[tmp]=true;
			if(i%primes[j]==0)break;
		}
	}
	return;
}

区间筛

求一段区间 \([L,R]\) 的素数

  • 利用埃筛思想

代码实现

  • 先筛出 \(\sqrt{n}\) 以内的小素数

  • 利用埃筛思想把小素数在 \([L,R]\) 中的倍数筛掉

细节部分

枚举小素数的时候要保证所筛的数的开始是 \([L,R]\) 以内的小素数的最小倍数。
可以用:

(l+x-1)/x*x;

板子题:

P1835 素数密度


void Block(int n){
	notp[1]=true;
	for(int i=2;i<=n;i++){
		if(!notp[i])primes[++cnt]=i;
		for(int j=1;j<=cnt;j++){
			int tmp=primes[j]*i;
			if(tmp>n)break;
			notp[tmp]=true;
			if(i%primes[j]==0)break;
		}
	}
	return;
}

void solve(int l,int r){
	for(int i=l;i<=r;i++)isp[i-l]=1;
	if(l==0)isp[0]=isp[1]=0;
	else if(l==1)isp[1]=0;
	for(int i=1;i<=cnt;i++){
		int x=primes[i];
		if(x*x>r)break;
		for(int j=(l+x-1)/x*x;j<=r;j+=x){
			if(j==x)continue;
			isp[j-l]=false;
		}
	}
}
posted @ 2021-08-12 16:48  ¶凉笙  阅读(24)  评论(0)    收藏  举报