2026年5月5日预计 7 分钟阅读OI

质数筛

zbl2012
zbl2012博主 & 创作者

质数筛也是数论中非常重要的一个部分。

根号筛

这个是很多人第一个学的筛法,也是最简单的筛法。

做法很简单,就是枚举 22n1n-1 里所有的数,如果当前数可以被 nn 整除,那么 nn 显然就是一个和数。直到跑完循环也没返回,就说明 nn 是一个指数。当然,n<2n<2 要特判,n=2n=2 也要特判。这样做每次判断一个数的时间复杂度是 O(n)O(n),判断 nn 个数的时间复杂度是 O(n2)O(n^2)。太慢了,要优化。

发现每当枚举的数 n\ge \sqrt n 时,这个数就已经被判断过了,所以只需要枚举 22n\sqrt n 的数就可以了,判断一个数的时间复杂度是 O(n)O(\sqrt n),判断 nn 个数的时间复杂度是 O(nn)O(n\sqrt n)注意点
1.注意循环到 n\sqrt n 时,不建议直接用

code
sqrt(n)
或者
code
i*i<=n
,前者慢,后者容易在 nn 大的时候爆掉。这里可以做一个移项,变成
code
i<=n/i
。 2.注意特判。

code

这里只给出判断的函数。

cpp
bool isprime(int x){
	if(x<2)return 0;
	if(x==2)return 1;
	for(int i=2;i<=x/i;i++){
		if(x%i==0)return 0;
	}
	return 1;
}

埃筛

埃筛,也叫埃氏筛,全称埃拉托斯特尼筛法。是一种高效的判断质数的筛法。

原理是这样的:因为每一个的倍数都是合数,所以可以从 22 开始筛,如果当前的数标记为质数,就把它的倍数标成合数,如果是合数则直接跳过。埃筛的数也只需要枚举到 n\sqrt n。 最后筛出来的标记数组就是每个数是质数还是合数的情况。 筛 nn 个数的时间复杂度是 O(nloglogn)O(n\log\log n),我也不知道怎么证。

值得一提的是,因为埃筛只用一个

code
bool 
数组来判断是否是质数,所以可以用
code
bitset
来替换
code
bool
数组,这样可以节省时间和空间。性能甚至可以超过接下来要讲的 O(n)O(n) 线性筛。 注意点 1.注意是
code
bitset<N>prime
,而不是
code
bitset<int>prime[N]
或者
code
bitset<bool>prime[N]
。 2.注意枚举 ii 的倍数时,要从 2i2i 开始,不然会把质数 ii 标记成合数。

code

如果使用

code
bool
数组,只需将
code
bitset<N>prime;
改成
code
bool prime[N];
P3912 素数个数

cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e8+10;
int n,q;
bitset<N>prime;
int primes;
int cnt=0;
void init(int n){
	for(int i=2;i<=n/i;i++){
		if(prime[i])continue;
		for(int j=i+i;j<=n;j+=i){
			prime[j]=1;
		}
	}
	for(int i=2;i<=n;i++){
		if(!prime[i])cnt++;
	}
	cout<<cnt;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n;
	init(n);
	return 0;
}

线性筛

线性筛又名欧拉筛。顾名思义,它的时间复杂度是线性的,也就是 O(n)O(n) 的。其实就是一种基于埃筛的优化。

当我们使用埃筛的时候,会发现,有一些数被重复筛了,比如 66 就同时被 2233 筛,这样多余的操作会使程序变慢,而线性筛是每个数只筛 11 遍的。

操作如下: 用一个

code
primes
数组来存筛出来的质数,一个
code
bool
数组
code
st
表示一个数字是质数还是合数。 从 22 开始遍历,每一个数先判断它是不是质数,如果是,则将他加入
code
primes
内。判断完之后,遍历整个
code
primes
数组,这里和前两个筛法一样,只有遍历到 n\sqrt n,把
code
primes[j]*i
标记成合数。重点来了,如果此时的 ii 可以整除
code
primes[j]
,就直接
code
break
,把这个数留给下一个因数去筛,因为此时
code
primes[j]
ii 的最小质因子,这样可以保证每个合数只被它的最小质因子筛掉。 注意点 1.遍历
code
primes
里的数的这步操作是每次都要跑的,也就是不过 ii 是质数或者合数,都要做的。

code

P3383 【模板】线性筛素数

cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e8+10;
int n,q;
int primes[N],cnt;
bool st[N];
void get_primes(int n){
	for(int i=2;i<=n;i++){
		if(!st[i])primes[cnt++]=i;
		for(int j=0;primes[j]<=n/i;j++){
			st[primes[j]*i]=true;
			if(i%primes[j]==0)break;
		}
	}
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>q;
	get_primes(n);
	while(q--){
		int k;
		cin>>k;
		cout<<primes[k-1]<<'\n';
	}
	return 0;
}

文章留言区

已有 0 条精彩探讨

正在拼命加载留言中...

发表您的见解

※ 提倡客观理性讨论。留言需要经过安全核查,请勿注入恶意链接。
上一篇文章组合数下一篇文章 题解:P3472 [POI 2008] MAF-Mafia