试除法判定质数
暴力做法:枚举 $2$ ~ $n-1$ 的所有数,判断能否将 $n$ 整除,如果存在一个数能把 $n$ 整数,说明 $n$ 不是质数
实际上只需要枚举到 $\sqrt{n}$ 即可,如果 $a$ 是 $n$ 的约数,那么 $\frac{n}{a}$ 也是 $n$ 的约数,我们只需要检验 $min(a,\frac{n}{a})$ 即可
bool is_prime(int x){
if (x == 1) return 0;//1不是质数
for (long long i = 2; i <= sqrt(x); i++)//从小到大枚举
if (x % i == 0) return 0;//如果能整除,立即返回非质数
return 1;//返回是质数
}
具体的计算中,i <= sqrt(x)
的操作可以优化为 i * i <= x
或 i <= x / i
,得到常数级的优化
不难看出对于判断 $n$ 而言,试除法判断质数的时间复杂度为 $O(\sqrt{n})$
埃拉托斯特尼筛法
这是一种在给定区间内有效筛选素数的方式,原理是:
- 从 $2$ 开始枚举每一个数,直到到达给定的界限
- 对每一个枚举的数都判断一次状态
- 枚举当前质数的倍数,他们必然是合数,更改状态
最后每个数都存在一个状态,要么被标记为合数,要么被标记为质数,这就完成了筛选素数的过程
bool st[N];//状态数组,合数被标记为 1,初始化时都为 0
int prime_mem[N];//记录素数
int cnt;//素数个数
void Eratosthens(int n){
for (long long i = 2; i <= n; i++){//从小到大枚举
if (!st[i]){//如果这个数是素数
prime_mem[++cnt] = i;//素数个数加 1,并记录
for (long long j = i * i; j <= n; j += i)//从小到大枚举素数的倍数
st[j] = 1;//标记素数
}
}
}
其中 for (long long j = i * i; j <= n; j += i)
从 i * i
开始标记合数,这是因为小于 i * i
的合数在之前已经被标记过了
证明如下:
- 设 $k < i\quad k\in N^+$,则 $ki $ 为 $i$ 的倍数,满足 $ki<i^2$
$$ ki<i^2 \iff k<i $$
- 存在质数$p$, $p<i$,$p$ 标记了它的倍数 $kp$ ,就有
$$ p\times k=p \iff k=1 $$
- 由于 $k<i$ ,所以下述不等式成立
$$ p\times k<p\times i<i^2 \iff p<i,k<i $$
所以任意的小于 $i^2$ 的合数,都已经被小于 $i$ 的一个素数 $p$ 标记过
显然,根据算数基本定理,要找到 $n$ 以内的所有质数,只需要对不超过 $\sqrt{n}$ 的素数进行筛除即可
for (long long i = 2; i <= n; i++)
$\rightarrow$ for (long long i = 2; i * i <= n; i++)
这种筛法的时间复杂度在 $O(n\log\log n)$ ,近乎于 $O(n)$
线性筛法/欧拉筛法
对于上面的埃拉托斯特尼筛法,过程中会对一个合数重复标记多次,通过线性筛法,可以做到让一个合数只被标记一次
那么时间复杂度就能降到 $O(n)$
原理如下:
- 合数总是可以分解为多个质数的积,(整数惟一分解定理)
- 使筛掉的方法唯一,每次通过合数的最小质因子筛掉
bool st[N];//状态数组,合数被标记为 1,初始化时都为 0
int prime_mem[N];//记录素数
int cnt;//素数个数
void Euler(int n){
for (long long i = 2; i <= n; i++){
if (!st[i]) prime_mem[++cnt] = i;
for (long long j = 1; i * prime_mem[j] <= n; j++){
st[i * prime_mem[j]] = 1;
if (i % prime_mem[j] == 0) break;//保证只被最小质因子筛除
}
}
}
$i$ 为质数,最多枚举到自身中断,即 $i^2\ \%\ i=0$ 。$i$ 为合数,最多枚举到最小质因子 $p$ 结束,即 $i*p\ \%\ i=0$
其中的 st[i * prime_mem[j]] = 1;
做到了具体的筛除过程
评论区(暂无评论)