阅读之前
上下文结合观看更有助于理解,建议理解了简单一些的埃式筛后再进行阅读,本文主要讲解欧拉筛
本文直接或间接涉及到的一些数学概念:
自然数:非负整数的集合。即一个数大于等于 0 ~0~ 0 ,且为整数
质数:自然数中除了 0 , 1 ~0,1~ 0,1 以外只能被 1 ~1~ 1 和自身整除的数
合数:自然数中除了 0 , 1 ~0,1~ 0,1 和质数以外的数
因子:一个整数 n ~n~ n 能够正好整除一个整数 m ~m~ m ,则 m ~m~ m 为 n ~n~ n 的因子,但是自身不为自身的因子
唯一分解定理:任何一个大于 1 ~1~ 1 的整数 n ~n~ n 都可以分解成若干个质因子的连乘积,如果不计各个质因子的顺序----比如 6 = 2 ∗ 3 = 3 ∗ 2 ~6=2*3=3*2~ 6=2∗3=3∗2 ,如果计因子排列顺序那么有两种分解顺序,不计因子排列顺序的话只有一种----那么这种分解是唯一的。由德国数学家高斯于1801年证明
废话
一般筛法 / 埃式筛:先定义一个大小为 m a x n ~maxn~ maxn 的数组代表将要进行筛选的 n ~n~ n 个数,然后基于每个质数的两倍及更高倍数为非质数的思想对数组进行筛选,最后未筛选的数即为质数
线性筛法 / 欧拉筛:在一般筛法的基础上优化所得,在 O ( n ) ~O(n)~ O(n) 的复杂度内求从 1 ~1~ 1 到 n ~n~ n 的范围内的质数。 避免了埃式筛对某些数字进行重复筛选的缺点,因此从 O ( n ∗ l o g l o g n ) ~O(n * log log n)~ O(n∗loglogn) 优化到 O ( n ) ~O(n)~ O(n)
结论:将每一轮进行筛选的数 x ~x~ x 表示为 i ∗ p r i m e [ j ] ~i * prime[ j ]~ i∗prime[j] (即 x = i ∗ p r i m e [ j ] ~x = i * prime[ j ]~ x=i∗prime[j] )。保证 i ~i~ i 为 x ~x~ x 的最大因子, p r i m e [ j ] ~prime[ j ]~ prime[j] 为 x ~x~ x 的最小因子(最小因子必为质数,如果不为质数,则最小因子可以分解为两个数的乘积, 与最小因子的定义矛盾)
数 x ~x~ x 能被分解成有限个质数相乘,(在这有限个质数当中可能包含了相同的质数,比如 300 ~300~ 300 可以分解为 2 ∗ 2 ∗ 3 ∗ 5 ∗ 5 ~2 * 2 * 3 * 5 * 5~ 2∗2∗3∗5∗5 ,将这有限个质数从小到大排列,取出其中最小的质数即为最小因子,再将剩下的数乘起来即为最大因子(比如 300 ~300~ 300 的最小因子为 2 ~2~ 2 ,最大因子为 150 ~150~ 150 )
在筛选过程中将用到:
一个数表 n u m [ m a x n ] ~num[maxn]~ num[maxn] 用于记录当前下标所代表的数是否为质数;
一个质数表 p r i m e [ m a x n ] ~prime[maxn]~ prime[maxn] 用于从小到大依次储存质数,一个 c n t ~cnt~ cnt 变量用于记录质数数量;
//质数表实际使用大小是不到 m a x n ~maxn~ maxn 的,因为一定范围内的质数数量一定是少于这个范围的大小的。一个范围 n ~n~ n 内大概有 n / l n ( n ) ~n/ln(n)~ n/ln(n) 个质数
两个变量 i ~i~ i 和 j ~j~ j 用于建立双层循环,外层循环的 i ~i~ i 用于遍历 n u m ~num~ num 数组作为最大因子;内层循环的 j ~j~ j 用于遍历 p r i m e ~prime~ prime 数组作为最小因子,同时筛除合数
主要问题在于如何筛选不遗漏、不重复,以及保证枚举最小因子
不遗漏和不重复的证明
除了
0
,
1
~0,1~
0,1 外,我们在用双层循环从小到大枚举最大因子
i
~i~
i 和最小因子
p
r
i
m
e
[
j
]
~prime[j]~
prime[j] 的乘积时,内层循环每次
i
~i~
i 和
p
r
i
m
e
[
j
]
~prime[j]~
prime[j] 的组合都是不同的,可以视为我们在从小到大枚举每一个唯一分解式,自然是不遗漏和不重复的。
枚举的起点是
2
~2~
2 。
2
~2~
2 同时也是最小的唯一分解式(除
0
,
1
~0,1~
0,1 外),
2
~2~
2 之后是
3
,
2
∗
2
,
5
,
2
∗
3
,
⋅
⋅
⋅
~3,2*2,5,2*3,···
3,2∗2,5,2∗3,⋅⋅⋅
因为每个数的质因数分解式都是唯一的(如果分解式不唯一,那么这个唯一的数就不唯一了),所以我们从小到大枚举唯一分解式一定能保证不遗漏和不重复
如何保证枚举最小因子
这样又带来了新的问题: 如何保证 p r i m e [ j ] ~prime[j]~ prime[j] 就是最小质因子呢? 因为我们必须保证 p r i m e [ j ] ~prime[j]~ prime[j] 为 x = i ∗ p r i m e [ j ] ~x=i*prime[j]~ x=i∗prime[j] 的最小因子, 才能满足不重复和不遗漏的要求
p r i m e ~prime~ prime 数组中所有小于等于 i ~i~ i 的最小质因子的质数 p r i m e [ j ] ( j = 0 , 1 , 2 , 3 , ∼ ) ~prime[j]~(j=0,1,2,3,\sim) prime[j] (j=0,1,2,3,∼)和 i ~i~ i 相乘后,都满足 p r i m e [ j ] ~prime[j]~ prime[j] 为 x = i ∗ p r i m e [ j ] ~x=i*prime[j]~ x=i∗prime[j] 最小质因子。在 p r i m e [ j ] ~prime[j]~ prime[j] 等于 i ~i~ i 的最小因子时结束对 i ~i~ i 的筛选即可
证明(反证法):
假设枚举的最大因子 i = p r i m e [ 3 ] ∗ p r i m e [ 4 ] ~i=prime[3]*prime[4]~ i=prime[3]∗prime[4] ,如果内层循环在 j = 3 ~j=3~ j=3 时仍然没结束筛选,接下来在 j = 4 ~j=4~ j=4 时,有 x = p r i m e [ 3 ] ∗ p r i m e [ 4 ] ∗ p r i m e [ 4 ] ~x=prime[3]*prime[4]*prime[4]~ x=prime[3]∗prime[4]∗prime[4] ,而 p r i m e [ 4 ] ~prime[4]~ prime[4] 非最小因子,这种情况下会与内层循环在 j = 3 ~j=3~ j=3 时, i = p r i m e [ 4 ] ∗ p r i m e [ 4 ] ~i=prime[4]*prime[4]~ i=prime[4]∗prime[4] 时重复。
所以,在 p r i m e [ j ] ~prime[j]~ prime[j] 等于 i ~i~ i 的最小因子时结束对 i ~i~ i 的筛选,可以保证枚举的 p r i m e [ j ] ~prime[j]~ prime[j] 为 x ~x~ x 的最小因子,否则 i ~i~ i 的最小因子会小于 p r i m e [ j ] ~prime[j]~ prime[j] ,与后面的筛选情况重复
比如:不能出现 i = 4 , p r i m e [ j ] = 3 ~ i = 4, prime [ j ] = 3~ i=4,prime[j]=3 乘积为 12 ~12~ 12 的组合, 因为 3 ~3~ 3 不为 12 ~12~ 12 的最小因子。如果不避免这种情况就会和 i = 6 , p r i m e [ j ] = 2 ~i=6,prime[j]=2~ i=6,prime[j]=2 乘积也为 12 ~12~ 12 的组合重复,而后者为正确的筛除方式
代码实现
const int maxn=1e5+1;
//全局变量自动初始化为0
bool num[maxn];
//1e5个数里质数数量肯定比1e5少的多, 这里随便除了个2优化一下空间, 也可以自己算一下n/ln(n)是多少
int prime[maxn / 2];
int cnt;
void Euler_Seive() {
//初始用1标记2及以上的数全为质数. 也可以把0作为质数标记, 然后修改num0和num1为1
for(int i = 2; i < maxn; i++)
num[i] = 1;
//i为最大因子
for(int i = 2; i < maxn; i++) {
//如果当前数未被筛除, 则加入质数表
if (num[i])
prime[cnt++] = i;
//j遍历prime数组, prime[j]作为最小因子
for (int j = 0; i * prime[j] < maxn; j++){
//筛除该数
num[i * prime[j]] = 0;
//当prime[j]为i的最小因子时退出for循环,
//因为当prime[j]大于i的最小因子时,
//n的最小因子存在于i的唯一分解式中
if (!(i % prime[j]))
break;
}
}
}
封面出处:AcWing 1356. 回文质数