线性筛小记

预计阅读时间: 5 分钟 840 次阅读 1038 字 最后更新于 2022-09-22 算法与数据结构


之前线性筛写的有点乱,重新整理一下,又看不懂的地方又改了改

前言

筛法是数论中的一个重要的思想,这篇文章介绍埃氏筛和它的优化欧拉筛.

思想

首先我们需要知道什么是素数和合数

我上高一才刚分清

  • 合数: 可以被1和它自己及其他数整除的.
  • 素数(质数): 只能被它自己和1整除的.

对于一个数(除了1,0),他大于1的整数倍一定是一个合数.

这个命题显然成立.

例如2

他大于1的整数倍4,6,8,10等,这些数都是合数.

我们的算法就是把非01的数字乘上多倍后,打上标记,标记这些是合数,最后没有标记的就是素数了.

核心代码

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(nloglogn)

而接下来要介绍的欧拉筛的时间复杂度可以达到O(n)

算法思想

这里的欧拉筛全部以筛素数为例

对于一个合数,欧拉筛会确保它只被筛一次。

我们必须明确一下几点:

  1. 任何合数都可以表示为多个素数之积(唯一分解定理)
  2. 每个合数必有一个最小质因数

我们只需找出每个数的最小质因数即可。

先放一下代码,后面继续讲。

代码

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)说明ians[j]的倍数,我们设ans[j]×k=i

我们反过来想,如果这里不退出的话,接下来会把ans[j+1]×i这个数筛掉,而ans[j+1]×i可以表示成ans[j+1]×k×ans[j],说明ans[j+1]×ians[j]的倍数,说明ans[j+1]不是最小最小质因数,再筛就重复了,所以要结束循环。

THE END