之前线性筛写的有点乱,重新整理一下,又看不懂的地方又改了改
前言
筛法是数论中的一个重要的思想,这篇文章介绍埃氏筛和它的优化欧拉筛.
思想
首先我们需要知道什么是素数和合数
我上高一才刚分清
- 合数: 可以被$1$和它自己及其他数整除的.
- 素数(质数): 只能被它自己和$1$整除的.
对于一个数(除了$1$,$0$),他大于$1$的整数倍一定是一个合数.
这个命题显然成立.
例如$2$
他大于$1$的整数倍$4$,$6$,$8$,$10$等,这些数都是合数.
我们的算法就是把非$0$和$1$的数字乘上多倍后,打上标记,标记这些是合数,最后没有标记的就是素数了.
核心代码
void sieve ()
{
composite[0] = true; //0和1都是合数
composite[1] = true;
for (register int i = 2; i <= n; i++) //一直筛到n
for (register int j = i + i; j <= n; j += i) //如果一个数是合数,那么它的倍数也一定是合数
composite[j] = true; //标记合数
}
食用方法(以洛谷P3383 【模板】线性筛素数)为例
#include <algorithm>
#include <cstdio>
#include <iostream>
#define maxn 10000005
using namespace std;
bool composite[maxn] = {};
int num = 0, n, m;
void sieve ()
{
composite[0] = true; //0和1都是合数
composite[1] = true;
for (register int i = 2; i <= n; i++) //一直筛到n
for (register int j = i + i; j <= n; j += i) //如果一个数是合数,那么它的倍数也一定是合数
composite[j] = true; //标记合数
}
int main ()
{
ios::sync_with_stdio (false); //关同步
cin.tie (0);
cout.tie (0);
cin >> n >> m;
sieve (); //筛一遍
int k;
while (m--)
{
cin >> k;
if (composite[k])
cout << "No" << endl;
else
cout << "Yes" << endl;
}
return 0;
}
欧拉筛二三事
其实没什么辣,欧拉筛只是埃筛的一个小优化。
我们分析一下埃筛的代码:
void sieve ()
{
composite[0] = true; //0和1都是素数
composite[1] = true;
for (register int i = 2; i <= n; i++) //一直筛到n
for (register int j = i + i; j <= n; j += i) //如果一个数是素数,那么它的倍数也一定是素数
composite[j] = true; //标记素数
}
注意这里:
for (register int j = i + i; j <= n; j += i) //如果一个数是素数,那么它的倍数也一定是素数
composite[j] = true; //标记素数
这段代码在无脑的筛掉元素,为什么称它“无脑”呢?
对于同一个元素,埃筛可能会将它筛掉好多次。
埃氏筛时间复杂度:$O(n\log\log n)$
而接下来要介绍的欧拉筛的时间复杂度可以达到$O(n)$
算法思想
这里的欧拉筛全部以筛素数为例
对于一个合数,欧拉筛会确保它只被筛一次。
我们必须明确一下几点:
- 任何合数都可以表示为多个素数之积(唯一分解定理)
- 每个合数必有一个最小质因数
我们只需找出每个数的最小质因数即可。
先放一下代码,后面继续讲。
代码
void eulerSieve()
{
composite[0] = true;
composite[1] = true;
for (register int i = 2; i <= n; i++)
{
if (!composite[i]) //是素数的话,我们就把它加入列表中
ans[++num] = i;
for (register int j = 1; j <= num && i * ans[j] <= n; j++)
//i * ans[j] <= n 判边界
{
composite[i * ans[j]] = true;
if (i % ans[j] == 0) // important!!!
break;
}
}
}
由于一个素数乘上了一个其他的数就成了合数,而欧拉筛是要确保每个合数都只被筛一次,而且只能被它的最小质因数筛掉。我们把素数都存在ans数组里。
$ if(i \% ans[j] == 0) $说明$i$是$ans[j]$的倍数,我们设$ans[j]\times k=i$
我们反过来想,如果这里不退出的话,接下来会把$ans[j+1]\times i$这个数筛掉,而$ans[j+1]\times i$可以表示成$ans[j+1]\times k\times ans[j]$,说明$ans[j+1]\times i$是$ans[j]$的倍数,说明$ans[j+1]$不是最小最小质因数,再筛就重复了,所以要结束循环。
Comments 1 条评论
博主 2962156371
写这么牛逼卧槽