素数
素数又称质数,是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
唯一分解定理:(下一章详细解释)
任何大于 1 的素数都能写作 N = p1cp2c2…pmcm 的形式。
素数分布定理:
对于正实数 x,定义 Π(x) 为不大于 x 的质数,则 Π(x) ≈ x / lnx
因此,第 n 个质数 P(n) ≈ nlnx
埃拉托斯特尼筛法
基本原理:
当需要求某一区间 [2, n] 内的所有素数时,可以从2开始,对于当前素数p,将 p2 后所有 p 的倍数筛去。
每次找到下一个没有被筛到的数就是一个素数。
时间复杂度:O(n*lglgn)
空间复杂度:O(n)
图示:
需加语句、头文件:
#include <cstdio>
int n; // [2, n] 区间
bool v[10010]; //标记
// v[i] = 0:该点是素数;
// v[i] = 1:该点非质数;
// v 数组声明在全局中,则其所有值都为 0,且所有数先默认为素数,再筛去合数。
实现代码:
scanf("%d", &n);
for (int i = 2; i <= n; ++i)
{
if(0 == v[i])
{
printf("%d ", i);
for (int j = i * i; j <= n; j += i)
v[j] = 1;
}
}
欧拉筛
有名的欧拉筛,我不知道谁发明的 ,知道他快就行了
核心思想:基于埃氏算法,但比埃式算法快很多。
埃氏算法类似最简单的素数筛,即先筛掉 2 的倍数,再筛掉 2 的下一个未被筛掉的数(即:3)的倍数,以此类推(5、7……的倍数)。
埃式算法的盲点就是很多合数会被扫描不止一次,如 42:它会被 2、3、7各自筛一遍。所以效率相对较低,时间复杂度为:O(NlogNlogN)
于是,欧拉筛在扫描点上做了处理,每次扫描的数绝对都是没被扫过的。
核心思想:
每次得到素数时,向下筛一遍,确保每次用且只用某数的最小质因数进行筛选。具体如下:
- 外层循环为 1→n,若 v[i] == 0,则表明其为质数(从未被筛过)。然后将其存放至 prime 数组中,写作
prime[++cnt] = i。然后内层从 1→cnt,若 i * prime[j] <= n,则筛去该数(该数一定未被筛过且为合数)。 - 接着,我们要使每个数只被其最小质因子筛去,则加入一条语句:如果 i % j == 0,则退出内层循环。
简单的正确性证明:
设一需要筛掉的合数 C 的最小质因数是 P,令 B = C / P(C=B × P),则 B 的最小质因数不小于 P(否则 C 的最小质因子不是 P)。那么当外层枚举到 i = B 时,便需要从小到大枚举素数;因为 i = B 的最小质因数不小于 P,所以 i 在质数枚举至 P 之前一定不会 break。于是,C 便会被 B × Pi 删去。
核心中的核心:恶心的 B 的最小质因数必不小于 P。
千万要注意,v[1] = 1,因为 1 不是质数
我在学校第一次集训考试时忘记这个,错了一个点,扣掉了五分。导致我就比第二名高了 8 分(老凡尔赛了)
需加语句、头文件:
#include <iostream>
int prime[10010];
bool v[10010];
int cnt = 0;
欧拉筛代码:
scanf("%d", &n);
v[1] = 1;
for (int i = 2; i <= n; ++i)
{
if(v[i] == 0)
prime[++cnt] = i;
// 这个 for 循环是所有数都参与的
for(int j = 1; j <= cnt && i * prime[j] <= n; ++j)
{
v[i * prime[j]] = 1;
// 这样能保证每个合数只被它的最小的质因子筛选一次
if(i % prime[j] == 0)
break;
}
}
作者:Rotch
日期:2020-09-25
修改:2020-09-25
[2021-05-03]:添加两个定理,增加一些解释