Acwing 质数

server/2024/10/19 5:29:46/

1.试除法判定质数

首先回顾一下什么是质数?

  • 对所有大于1的自然数,如果这个数的约数只包含1和它本身,则这个数被称为质数或者素数

试除法:对于一个数n,从2枚举到n-1,若有数能够整除n,则说明除了1和n本身,n还有其它约数,则n不是质数;否则,n是质数;

  • 优化:由于一个数的约数都是成对出现的。比如12的一组约数是3,4,另一组约数是2,6。则我们只需要枚举较小的那一个约数即可。我们用d|n来表示d整除n,只要满足d|n,则一定有{n/d}|n,比如3∣12,则{12/3} | 12,因为约数总是成对出现的,我们只需要枚举小的那部分数即可,令d ≤ n/d,即,d ≤ sqrt{n},因此对于n,只枚举2到sqrt{n}即可。
  • 注意:for循环的结束条件,推荐写成i <= n / i。有的人可能会写成i <= sqrt(n),这样每次循环都会执行一次sqrt函数,而这个函数是有一定时间复杂度的。而有的人可能会写成i * i <= n,这样当i很大的时候(比如i比较接近int的最大值时),i * i可能会溢出,从而导致结果错误。

Acwing 866.试除法判定质数
在这里插入图片描述
具体实现代码(详解版):

#include <iostream>  
using namespace std;//试除法:用于判断传入的整数 x 是否为素数
bool is_prime(int x){// 如果 x 小于 2,则不是素数,返回 falseif(x < 2) return false;// 从 2 开始迭代,直到 i * i <= x (相当于 i <= sqrt(x)),检查是否有因数for(int i = 2; i <= x / i; i++){// 如果 x 能被 i 整除,说明 x 不是素数,返回 falseif(x % i == 0)return false;}// 如果没有找到任何因数,返回 true,表示 x 是素数return true;
}int main(){int n, x;  cin >> n;  while(n --){  cin >> x;  // 如果 x 是素数,输出 "Yes",否则输出 "No"if(is_prime(x)) puts("Yes");else puts("No");}return 0; 
}

2.质因数分解:试除法

对于一个整数 N 总能写成如下形式:
N = P 1 α 1 × P 2 α 2 × P 3 α 3 ⋯ × P n α n N=P_{1} ^{α_1} \times P_{2} ^{α_2} \times P_{3} ^{α_3} \dots \times P_{n} ^{α_n} N=P1α1×P2α2×P3α3×Pnαn
其中Pi都是质数,αi为大于0的正整数,即一个整数可以表示为多个不同质数的次方的乘积

  • 对于一个数求质因数的过程:从2到n,枚举所有数,依次判断是否能够整除n即可。朴素法,时间复杂度O(n))。;
  • 优化:n中只包含一个大于sqrt{n}的质因子,很好证明,如果中包含两个大于sqrt{n}的质因子,那么乘起来就大于n了。因此,在枚举的时候可以先把2到sqrt{n}的质因子枚举出来,如果最后处理完n > 1,那么这个数就是那个大于\sqrt{n}的质因子,单独处理一下就可以。时间复杂度降为O(sqrt(n))

求质因数分解,为什么枚举所有数,而不是枚举所有质数,万一枚举到合数怎么办?解释:枚举数时,对于每个能整除 n 的数 i先把这个数除干净了(就是把这个质数的次方剔除了,表现在上式中就是逐步去除Pi^αi),再继续枚举后面的数,这样能保证,后续再遇到能整除的数,一定是质数而不是合数。

例如:求180的质因数分解

  1. i = 2 n = 180 / 2 = 90 / 2 = 45
  2. i = 3 n = 45 / 3 = 15 / 3 = 5
  3. i = 4 当i是合数时,i 一定不能整除 n 。如果 4 能整除 n 那么 2 一定还能整除 n,就是在 i = 2的时候没有除干净,而我们对于每个除数都是除干净的,因此产生矛盾。
  4. i = 5 n = 5 / 5 = 1

Acwing 867.分解质因数
在这里插入图片描述
具体实现代码(详解版):

#include <iostream>  using namespace std;// 使用试除法分解质因数
void divide(int x){// 从 2 开始迭代,直到 i * i <= x (相当于 i <= sqrt(x)),试图找到因数for(int i = 2 ; i <= x / i ; i ++){// 如果 x 能被 i 整除,则 i 是 x 的一个质因数if(x % i == 0){int s = 0;  // 计数 i 的出现次数// 用 while 循环找出 i 作为因子的次数,直到 x 不能被 i 整除while(x % i == 0) x /= i, s ++;cout << i << ' ' << s << endl;}}// 如果 x 最后还大于 1,说明剩下的 x 是一个大于 sqrt(x) 的质数if(x > 1) cout << x << ' ' << 1 << endl; cout << endl;  
}int main(){int n, x;  cin >> n;  while(n --){  cin >> x;  divide(x); }return 0;  
}

3.筛质数–朴素法

将2到n全部数放在一个集合中,遍历2到n,每次删除当前遍历的数在集合中的倍数。最后集合中剩下的数就是质数。

解释:如果一个数p没有被删掉,那么说明在2到p-1之间的所有数,p都不是其倍数,即2到p-1之间,不存在p的约数。故p一定是质数。

时间复杂度:
n 2 + n 3 + ⋯ + n n = n ln ⁡ n < n log ⁡ 2 n \frac{n}{2} + \frac{n}{3} + \dots +\frac{n}{n} = n\ln_{}{n} < n\log_{2}{n} 2n+3n++nn=nlnn<nlog2n
故,朴素思路筛选质数的时间复杂度大约为O(nlogn)

Acwing 868.筛质数
在这里插入图片描述
具体实现代码(详解版):

#include <iostream>
using namespace std;const int N = 1000010; 
int primes[N], cnt;     // primes 数组存储所有找到的素数,cnt 计数素数的个数
bool st[N];             // st 数组标记数字是否被筛掉,false 表示未筛掉,true 表示已筛掉// 朴素筛法,用于筛选出小于等于 n 的所有素数
void get_prime(int n) {// 从 2 开始遍历到 n,逐个检查数字是否是素数for (int i = 2; i <= n; i++) {// 如果 st[i] 为 false,说明 i 没有被筛掉,因此 i 是素数if (!st[i]) primes[cnt++] = i;// 将所有 i 的倍数标记为 true,表示这些数不是素数for (int j = i + i; j <= n; j += i) {st[j] = true;}}
}int main() {int n;cin >> n;  get_prime(n);  cout << cnt << endl;  return 0;
}

4.筛质数–埃氏筛法

在上面朴素筛法的基础上,

其实不需要把全部数的倍数删掉,而只需要删除质数的倍数即可

对于一个数p,判断其是否是质数,其实不需要把2到p-1全部数的倍数删一遍,只要删掉2到p-1之间的质数的倍数即可。因为,若p不是个质数,则其在2到p-1之间,一定有质因数,只需要删除其质因数的倍数,则p就能够被删掉。埃氏筛法筛选质数的时间复杂度大约为O{nlog(logn)}

具体实现代码(详解版):

#include <iostream>  
using namespace std;const int N = 1000010; 
int primes[N], cnt;     // primes 数组存储所有素数,cnt 记录素数的个数
bool st[N];             // st 数组用于标记每个数是否被筛掉,false 表示未筛掉// 埃拉托斯特尼筛法,找出小于等于 n 的所有素数
void get_prime(int n) {// 从 2 开始遍历,检查每个数是否是素数for (int i = 2; i <= n; i++) {// 如果 i 没有被筛掉,则 i 是一个素数if (!st[i]) {primes[cnt++] = i;  // 将素数存储到 primes 数组中,并增加素数个数计数// 将 i 的所有倍数标记为非素数,从 i * 2 开始,每次增加 ifor (int j = i + i; j <= n; j += i)st[j] = true;  // 标记 i 的倍数为 true,表示它们不是素数}}
}int main() {int n;cin >> n;  // 输入一个整数 n,表示筛选范围为 2 到 nget_prime(n);  // 调用 get_prime 函数进行素数筛选cout << cnt << endl;  // 输出素数的个数return 0;
}

5.筛质数–线性筛法

大体思路和埃氏筛法一样,将合数用他的某个因数筛掉,其性能要优于埃氏筛法(在 1 0 6 10^{6} 106下两个算法差不多,在10^7下线性筛法大概快一倍)核心思路是:对于某一个合数n,其只会被自己的最小质因子给筛掉,从而避免了重复标记

设置一个primes数组,存储质数(以下叙述用pj来表示primes[j]),从2到n进行循环遍历,用数组st[]标记是否为质数。每次循环都对当前质数数组进行遍历,用其最小质因子筛除合数

  • i % pj == 0时:pj 一定是 i 的最小质因子,因为我们是从小到大枚举质数的,首先遇到的满足i % p j == 0的,pj 一定是 i 的最小质因子并且pj 一定是pj * i的最小质因子。比如,15 = 3 *5,15的最小质因子是3,则15的倍数中最小的数,其最小质因子同样是3的,15乘以最小质因子3,即45;
  • i % pj != 0时:pj 一定不是 i 的质因子,并且由于是从小到大枚举质数的,那么 pj 一定小于 i 的全部质因子。那么 pj 就一定是 pj * i 的最小质因子

具体实现代码(详解版):

#include <iostream>
using namespace std;const int N = 1000010;  
int primes[N], cnt;     // primes 数组存储所有找到的素数,cnt 记录素数的个数
bool st[N];             // st 数组标记数字是否被筛掉,false 表示未筛掉// 线性筛法,筛选出小于等于 n 的所有素数
void get_prime(int n) {// 遍历 2 到 n,逐个判断每个数是否为素数for (int i = 2; i <= n; i++) {// 如果 st[i] 为 false,则 i 是素数,将其存入 primes 数组if (!st[i]) primes[cnt++] = i;// 遍历所有已找到的素数 primes[j]for (int j = 0; primes[j] <= n / i; j++) {// 将 i 与当前素数 primes[j] 的乘积标记为合数st[primes[j] * i] = true;// 如果 i 是 primes[j] 的倍数,停止筛选,因为后面的乘积已经包含该素数if (i % primes[j] == 0) break;}}
}int main() {int n;cin >> n; get_prime(n);  cout << cnt << endl; return 0;
}

对比总结:

筛法名称时间复杂度空间复杂度算法思想优缺点
朴素筛法O(n√n)O(1)对于每个数 i,依次检查它是否是素数,若是素数,将其倍数标记为合数。优点:实现简单,代码直观。缺点:时间复杂度较高,筛选效率低。
埃拉托斯特尼筛法O(n log log n)O(n)从 2 开始,对于每个素数 i,将它的所有倍数标记为合数。优点:效率比朴素筛法高。缺点:每个合数可能被多次标记(即被多个素数筛掉)。
线性筛法O(n)O(n)对每个数 i,只用它的最小质因子来筛选它的倍数,保证每个合数只被标记一次。优点:筛选效率最高,时间复杂度接近线性。缺点:实现稍复杂,理解难度较高。

以上基本可以解决所有素数的问题,多写几遍板子。


http://www.ppmy.cn/server/123113.html

相关文章

【洛谷】P1012 [NOIP1998 提高组] 拼数

复盘&#xff1a;我刚开始的思路就是复位sort比大小 因为要排最大的整数 只需要把按第一位数最大的排前面&#xff08;贪心&#xff09; 如果相同就排第二位数 以此类推 但是我最后只能得75分 不知道是哪里出问题了 改了一个bug就是 数据 4 8500 850 85 11 结果应该是85850850…

[Redis][持久化][下][AOF]详细讲解

目录 1.AOF1.是什么&#xff1f;2.使用AOF3.命令写入4.文件同步5.重写机制6.启动时数据恢复7.混合持久化 2.总结 1.AOF 1.是什么&#xff1f; AOF(Append Only File)持久化&#xff1a;以独⽴⽇志的⽅式记录每次写命令&#xff0c;重启时再重新执⾏AOF⽂件中的命令达到恢复数…

自然语言处理在人工智能领域的发展历程,以及NLP重点模型介绍

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下自然语言处理在人工智能领域的发展历程&#xff0c;以及NLP重点模型介绍。本文详细介绍了自然语言处理的发展历程&#xff0c;同时深入探讨了各种自然语言处理模型的原理与应用。文章首先回顾了自然语言处理技术的发…

SMTP/IMAP服务发在线邮件时要用到

SMTP/IMAP服务 require PHPMailerAutoload.php; // 或 require class.phpmailer.php;// 创建实例 $mail new PHPMailer();// 设定邮件服务器 $mail->isSMTP(); $mail->Host smtp.example.com; // 邮件服务器地址 $mail->SMTPAuth true; $mail->Username your…

jupyter安装与使用——Ubuntu服务器

jupyter安装与使用——Ubuntu服务器 一、安装miniconda3/anaconda31. 下载miniconda32. 安装miniconda33. 切换到bin文件夹4. 输入pwd获取路径5. 打开用户环境编辑页面6. 重新加载用户环境变量7. 初始化conda8.验证是否安装成功9.conda配置 二、安装jupyter2.1 conda安装2.2 配…

这条挣钱的路,离我好遥远啊

近日&#xff0c;笔者在发表的《乱篇弹&#xff08;54&#xff09;让子弹飞》一文中写道&#xff1a;“ 当然&#xff0c;笔者在《博客中国-狼头长啸的作家专栏》耕耘期间&#xff0c;也赚了一些用以补贴自己养老的‘ 散碎银两’。那么笔者是否可以依照知乎网的‘申请开通权限’…

R语言 基础 笔记 3

起因, 目的: 思考一个问题: AI 这么强,AI 什么都知道,为什么还要学习这些基础的东西, 为什么还要写这些笔记? 我觉得,大体过一遍,还是有好处的。 有个大致印象,下次查的时候,也方便一些。 几个函数 cbind() 按照列,拼接数据, 会改变某些列的数据类型。data() 查看…

Xcdoe快速更新安装的小Tips

1. 下载Xcdoe 从AppStore更新估计有些慢的话&#xff1b; 可用下载工具从苹果开发者网站直接下载&#xff1a;https://developer.apple.com/download/all/下载完成后解压出来的 Xcode App文件 可以直接拖入 应用程序 文件夹&#xff0c;选择 替换 即可&#xff1b; 2. 下载模…