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