当前位置: 首页 > news >正文

【数论】质数筛(埃氏筛、欧拉筛)

文章目录

  • 一、质数的判定
    • 1. 质数与合数
    • 2. 试除法判断质数
  • 二、质数筛
    • 0. 引入
    • 1. 埃氏筛
    • 2. 欧拉筛(线性筛)
    • 3. 【模板】质数筛 ⭐⭐
  • 三、OJ 练习
    • 1. Goldbach's Conjecture ⭐⭐
      • (1) 解题思路
      • (2) 代码实现
    • 2. 素数密度 ⭐⭐⭐
      • (1) 解题思路
      • (2) 代码实现

一、质数的判定

1. 质数与合数

一个大于 1 的自然数,除了 1 和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数。其中,质数又称素数

规定:1 既不是质数也不是合数。


2. 试除法判断质数

对于一个数 x x x ,根据定义,我们可以从 [2, x − 1] 这么多个数中一个一个尝试,判断 x x x 能否被整除。时间复杂度为 O ( x ) O(x) O(x)

但是,我们其实没有必要每一个数都去判断。因为 a a a 如果是 x x x 的约数,那么 x / a x \ /\ a x / a 也是 x x x 的约数。因此,我们仅需判断较小的 a a a 是否是 x x x 的约数,没有必要再去看看 x / a x \ /\ a x / a。那么,仅需枚举到 x \sqrt{x} x 即可。时间复杂度为 O ( x ) O(\sqrt{x}) O(x )

#include<iostream>using namespace std;// 判断 x 是否为质数
bool isPrime(int x)
{if(x <= 1) return false;// i <= x / i 是防溢出的写法, 不建议写 i * i <= xfor(int i = 2; i <= x / i; i++){// 如果 i 是 x 的约数,那么 x 一定不是质数if(x % i == 0) return false;}return true;
}int main()
{int n;cin >> n;int x;while(n--){cin >> x;if(isPrime(x)) cout << x << " ";}return 0;
}

二、质数筛

0. 引入

上面我们学习了如何判断一个数是否是素数,但是如果此时想知道 [1, n] 中有多少个素数呢?或者是 [1, n] 中的素数里面,第 k k k 个素数是多少,我们该怎么做呢?

一个自然的想法就是从 2 开始,依次向后对每一个自然数进行一次素数检验。这种解法相对暴力,下面介绍两种方法,能够快速地将 [1, n] 中的素数全部筛选出来。


1. 埃氏筛

【核心思想】:素数的倍数一定不是素数。

因此,如果想要筛选出 [1, n] 中的所有素数,我们从 2 开始依次考虑每个数,同时把当前这个数的所有倍数(最大到 n)标记为合数,没有被标记的数就是素数。

typedef long long LLbool st[N];  // 当前这个数有没有被标记为合数
int p[N];  // 记录素数
int cnt;  // 素数的个数// 埃氏筛,把 1-n 之间的所有素数筛选出来, 放到 p 数组中
void get_prime(int n)
{for(LL i = 2; i <= n; i++){if(!st[i])  // 如果当前数没有被标记,说明是素数{p[++cnt] = i;  // 记录这个素数// 把这个数的倍数都标记为合数// 小优化: 我们没有必要从这个素数 i 的 2 倍开始标记,可以直接从 i 的 i 倍开始标记for(LL j = i * i; j <= n; j += i){st[j] = true;}}}
}

埃氏筛的时间复杂度为: O ( n log ⁡ log ⁡ n ) O(n\log \log n) O(nloglogn),证明从略。


2. 欧拉筛(线性筛)

【核心思想】:在埃氏筛的基础上,让每一个合数被它的最小质因数筛掉。

在埃氏筛中,我们不难发现有很多数可能会被标记过不止一次,比如 12 是一个合数,在埃氏筛筛选的过程中,它会被 2 的 6 倍标记,也会被 3 的 4 倍标记。这样就会导致时间复杂度变大,那么欧拉筛针对这一点进行了优化。这个筛法的大致过程和埃氏筛很像,但是它在筛选的过程中能够保证每一个数都只被标记一次。我们的做法是,让每一个合数被它的最小质因数筛掉

欧拉筛(线性筛)的时间复杂度为: O ( n ) O(n) O(n)

typedef long long LLbool st[N];  // 当前这个数有没有被标记为合数
int p[N];  // 记录素数
int cnt;  // 素数的个数void get_prime(int n)
{for(LL i = 2; i <= n; i++){if(!st[i]) p[++cnt] = i;  // 没有被标记过,就是素数// 开始标记for(int j = 1; i * p[j] <= n; j++){st[i * p[j]] = true;if(i % p[j] == 0) break;  // 欧拉筛的核心/*这个判定条件能让每一个合数被自己的最小素因数筛掉。 1. 如果 i 是合数,枚举到最小素因数的时候跳出循环 2. 如果 i 是素数,枚举到自身时跳出循环 */}}
}

3. 【模板】质数筛 ⭐⭐

【题目链接】

P3383 【模板】线性筛素数 - 洛谷

【题目描述】

如题,给定一个范围 n n n,有 q q q 个询问,每次输出第 k k k 小的素数。

【输入格式】

第一行包含两个正整数 n , q n,q n,q,分别表示查询的范围和查询的个数。

接下来 q q q 行每行一个正整数 k k k,表示查询第 k k k 小的素数。

【输出格式】

输出 q q q 行,每行一个正整数表示答案。

示例一

输入

100 5
1
2
3
4
5

输出

2
3
5
7
11

【说明/提示】

对于 100 % 100\% 100% 的数据, n = 1 0 8 n = 10^8 n=108 1 ≤ q ≤ 1 0 6 1 \le q \le 10^6 1q106,保证查询的素数不大于 n n n


#include<iostream>typedef long long LL;const int N = 1e8 + 10;
bool st[N];
int p[N], cnt;
int n, q;// // 埃氏筛
// void get_prime(int n)
// {
//     for(LL i = 2; i <= n; i++)
//     {
//         if(!st[i])
//         {
//             p[++cnt] = i;
//             for(LL j = i * i; j <= n; j += i)
//             {
//                 st[j] = true;
//             }
//         }
//     }
// }// 欧拉筛
void get_prime(int n)
{for(LL i = 2; i <= n; i++){if(!st[i]) p[++cnt] = i;for(int j = 1; i * p[j] <= n; j++){st[i * p[j]] = true;if(i % p[j] == 0) break;}}
}int main()
{scanf("%d%d", &n, &q);get_prime(n);while(q--){int i;scanf("%d", &i);printf("%d\n", p[i]);}return 0;
}

三、OJ 练习

1. Goldbach’s Conjecture ⭐⭐

【题目链接】

UVA543 Goldbach’s Conjecture - 洛谷

【题目描述】

哥德巴赫猜想的内容如下:

任意一个大于 4 4 4 的偶数都可以拆成两个奇质数之和。

比如:

$
\begin{aligned}
8&=3+5\
20&=3+17=7+13\
42&=5+37=11+31=13+29=19+23
\end{aligned}
$

你的任务是:验证小于 1 0 6 10^6 106 的数满足哥德巴赫猜想。

【输入格式】

输入包含多组数据。

每组数据占一行,包含一个偶数 n ( n ≤ 1 0 6 ) n(n \le 10^6) n(n106)。 读入以 0 0 0 结束。

【输出格式】

对于每组数据,输出形如 n = a + b,其中 a , b a,b a,b 是奇质数。若有多组满足条件的 a , b a,b a,b,输出 b − a b−a ba 最大的一组。

若无解,输出 Goldbach's conjecture is wrong.

【示例一】

输入

8
20
42
0

输出

8 = 3 + 5
20 = 3 + 17
42 = 5 + 37

(1) 解题思路

[ 2 , 1 0 6 ] [2, 10^6] [2,106] 内的所有质数用线性筛筛出来,然后从前往后枚举筛出来的每一个奇质数 p p p,检查 n − p n - p np 是否也是奇质数,如果是那么 n n n 就可以表示为 ( p ) + ( n − p ) (p) + (n - p) (p)+(np)


(2) 代码实现

#include<iostream>using namespace std;typedef long long LL;const int N = 1e6 + 10;
bool vis[N];
int p[N];
int cnt;// 线性筛
void get_prime()
{int n = 1e6;for(LL i = 2; i <= n; i++){if(!vis[i]) p[cnt++] = i;for(int j = 0; p[j] * i <= n; j++){vis[p[j] * i] = true;if(i % p[j] == 0) break;}}
}int main()
{get_prime();int n;while(1){cin >> n;if(n == 0) break;for(int i = 1; i < cnt; i++){int x = p[i];int y = n - x;if(!vis[y]){printf("%d = %d + %d\n", n, x, y);break;}}}return 0;
}

2. 素数密度 ⭐⭐⭐

【题目链接】

P1835 素数密度 - 洛谷

【题目描述】

给定 L , R L,R L,R,请计算区间 [ L , R ] [L,R] [L,R] 中素数的个数。

1 ≤ L ≤ R < 2 31 1\leq L\leq R < 2^{31} 1LR<231 R − L ≤ 1 0 6 R-L\leq 10^6 RL106

【输入格式】

第一行,两个正整数 L L L R R R

【输出格式】

一行,一个整数,表示区间中素数的个数。

【示例一】

输入

2 11

输出

5

(1) 解题思路

由于这道题的右区间过大,所以我们不能之间开这么大的数组去筛素数。但是区间的长度并不大,因此我们可以考虑先筛出 [ 1 , R ] [\ 1, \sqrt{R}\ ] [ 1,R  ] 中的所有素数,然后用这些素数的倍数去把 [ L , R ] [L, R] [L,R] 区间内的合数打上标记,最后没有被标记的就是素数。

比如, L = 16 , R = 26 L = 16, R = 26 L=16R=26,那么我们就先筛出 26 \sqrt{26} 26 以内的所有素数 [ 2 , 3 , 5 ] [2, 3, 5] [2,3,5],之后我们用这三个素数的倍数去标记 [ 16 , 26 ] [16, 26] [16,26] 中的合数。我们首先需要用 2 2 2 8 8 8 倍(从 2 2 2 倍开始就慢了)把 16 16 16 标记为合数,之后是 18 , 20 , . . . 26 18, 20 ,\ ... \ 26 18,20, ... 26。这里的 8 8 8 是由 L / 2 L \ /\ 2 L / 2 向上取整得到的。

之后用 3 3 3 ⌈ 16 / 3 ⌉ = 6 \lceil16/3\rceil = 6 16/3=6 倍把 18 18 18 标记为合数,以此类推。

最后遍历 [ L , R ] [L, R] [L,R],没有被标记的就是素数。

  • 细节问题

L L L 有可能是我们在 [ 1 , R ] [\ 1, \sqrt{R}\ ] [ 1,R  ] 中筛出来的素数,比如 L = 5 , R = 26 L = 5, R = 26 L=5,R=26,此时我们筛出来的素数为 [ 2 , 3 , 5 ] [2, 3, 5] [2,3,5]。那么我们用 5 5 5 去标记时我们会把 5 5 5 也标记为合数,此时我们至少需要从 5 5 5 2 2 2 倍开始去标记,那么我们只需取 2 2 2 ⌈ L / i ⌉ \lceil L/i\rceil L/i 的较大值即可。

  • ⌈ x / y ⌉ \lceil x\ / \ y\rceil x / y 用代码实现时可以用 (x + y - 1) / y 的写法。

(2) 代码实现

#include<iostream>
#include<cmath>using namespace std;typedef long long LL;const int N = 1e6 + 10;
int p[N];
bool vis1[N], vis2[N];  // 分别用来标记 1-sqrt(R) 和 L-R 中的合数
int l, r, cnt;// 欧拉筛筛 1-sqrt(R) 中的素数
void get_prime(int n)
{if(n <= 1) return;for(LL i = 2; i <= n; i++){if(!vis1[i]) p[cnt++] = i;for(int j = 0; i * p[j] <= n; j++){vis1[i * p[j]] = true;if(i % p[j] == 0) break;}}
}// 计算 L-R 中的素数个数
int prime_cnt(int l, int r)
{// 1 不在我们的考虑范围内if(l == 1) l = 2;get_prime(sqrt(r));// 用 1-sqrt(R) 中的每一个素数去标记 L-R 中的合数for(int i = 0; i < cnt; i++){LL x = p[i];// j 表示需要标记的合数for(LL j = max(2 * x, (x + l - 1) / x * x); j <= r; j += x){vis2[j - l] = true;}}// 遍历 vis2 数有多少个素数int cnt = 0;for(int i = l; i <= r; i++){if(!vis2[i - l]) cnt++;}return cnt;
}int main()
{cin >> l >> r;cout << prime_cnt(l, r) << endl;return 0;
}
http://www.dtcms.com/a/519435.html

相关文章:

  • 扩展名网站兰州做网站一咨询兰州做网站公司
  • 华为OD-Java面经-21届考研
  • Excel拆分和合并优化版本
  • 智能网联汽车:当汽车遇上“智慧网络”
  • 常规点光源在工业视觉检测上的应用
  • C++新特性——正则表达式
  • 基于卷积神经网络的汽车类型识别系统,resnet50,vgg16,resnet34【pytorch框架,python代码】
  • 设计 企业网站电脑系统网站建设
  • 做网站业务的怎么找资源网站推广名片
  • FPGA强化- HDMI显示器驱动设计与验证
  • 【PPT-ungroup PPT解组合,python无水印】
  • Java 17 环境下 EasyPoi 反射访问异常分析与解决方案(ExcelImportUtil.importExcelMore)
  • SpringBoot+alibaba的easyexcel实现前端使用excel表格批量插入
  • 重大更新,LVGL有UI编辑器用了
  • 多场景 VR 教学编辑器:重构教学流程的场景化实践
  • 公司做网站让我们销售单页面网站模板怎么做
  • 广州微网站建设价位广东网站建设公司
  • 基于 Spring AI Alibaba + Nacos 的分布式 Multi-Agent 构建指南
  • 《与幽灵作战:Python 棘手 Bug 的调试策略与实战技巧》
  • 使用Requests和lxml实现飞卢小说网小说爬取
  • bug 记录 - 路由守卫 beforeRouteLeave 与 confirm 结合,不生效问题
  • 数据库字段类型bit容易被忽视的bug
  • centos 配置网络
  • [人工智能-大模型-55]:模型层技术 - AI的算法、数据结构中算法、逻辑处理的算法异同
  • LeetCode 3461.判断操作后字符串中的数字是否相等 I:简单题简单做的时候到了
  • IPhone 17 Pro Max拍摄专业画质视频教程
  • MoE大模型分布式训练:Switch Transformer与专家并行策略
  • 网站设置评价青岛企业网站建设优化
  • MySQL 增删改查操作与 SQL 执行顺序
  • 静态Web应用与JavaScript:现代前端开发的新范式