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

算法中的数学:质数(素数)

1.质数

1.1定义

一个大于1的自然数,除了1和它自身外,不能被其他自然数整除,那么他就是质数,否则他就是合数。

注意:1既不是质数也不是合数

唯一的偶质数是2,其余所有质数都是奇质数

1.2质数判定求法

试除法:

若n为1,说明不是质数,

若n不为1,

我们从2开始遍历到n-1,判断2到n-1是否可以被n整除,如果任意(2,n-1)的数可以被整除,那么他就不是质数。如果都不能被整除,那么就是质数


上面的试除法其实还不够好,因为需要遍历的次数接近n次,而我们其实可以再剪掉一部分数不用判断。

结论:只要遍历从2到根号n的自然数

讲解:

若n有一个大于根号n的因数,那么它一定有一个小于根号n的因数(因为这样两个因数相乘才会等于n)

而一个合数一定有一个因数小于等于根号n,所以我们只要判断从2到根号n的自然数是否有关于n的因数就可以知道该数是合数还是质数了

若存在一个可以整除的数i,那么这个数n就是合数,否则他就不是合数,而是质数

代码实现:

bool isprime(int num)
{if(num == 1) return false;for(int i = 2; i <= num/i; i++){if(num%i == 0){return false;}}return true;
}

2.更多应用方法

若我们想知道区间(1,n)中一共有多少个素数,且想知道第k个素数是谁。应该如何求?

暴力解法:遍历判断(1,n),用全局变量count记录一共出现的素数个数,用数组f[k]记录第k个素数是谁

不过我们其实有其他更好的解法可以用,接下来我们分别介绍


注意:区间一定要是从1或2开始的连续区间才可以用后面的方法

2.1埃氏筛

 对于一个大于1的正整数,他的k倍一定是一个合数

所以我们在访问到当前数num的时候就可以顺便把他的所有倍数剪枝掉,他的所有倍数一定不是素数

而如果我们是从i==2开始遍历的,只要我们可以访问到的数就一定是质数,这可以用反证法推导。

图示解析前三轮:

被框柱的就是判定为素数的,被划掉的表示经过倍数筛查将这些合数筛掉了,我们看到这里其实有一些数被筛掉了两次,这是因为他们是某些质数的公倍数,而我们其实可以通过优化让我们在筛查的时候减少重复筛

优化:

我们在倍数筛查的时候可以直接从i的i倍开始,因为i的2倍到i-1倍都被前面的质数倍数筛掉了

代码实现:

typedef long long ll;
const int N = 1e9;
bool f[N];
int a[N];
int count;
void get_prime(int n)
{for(int i = 2; i <= n; i++){if(f[i] == true){ continue;}a[++count] =  i;for(ll j = i*i ; j <= n; j+=i){f[j] = true;}}
}

2.2线性筛

 由于埃氏筛即使经过从i乘i开始的优化,仍然会有合数被重复筛,时间复杂度并不是O(n)级别。

为了使筛选的时间复杂度降低为O(n)级别,我们需要使用线性筛


线性筛最多只会对每个元素访问两次,一次是遍历到的时候,一次是筛掉的时候。

核心逻辑:让每个合数被其最小质因数筛掉

代码实现核心逻辑:

从前往后遍历数i,用i的质数倍去筛,当i是当前的质数的倍数时,进行完当前筛查后停止

代码实现:
 

typedef long long ll;
const int N = 1e9;
bool f[N];
int a[N];
int count;
void get_prime(int n)
{for(ll i = 2; i <= n; i++){
//没有被标记为true,说明是质数if(f[i] == false) {a[++count] = i;}
//进行线性筛for(int j = 1; i*a[j] <= n; j++){f[i*a[j]] = true;if(i % a[j] == 0) break;{          }
}

注意:

1.如果i是合数,那么他会枚举到最小质因数后退出循环;如果i是质数,那么他会枚举到等于它本身的质数后退出循环。

而这个特性表明:p[j]就是i的最小质因数

2.线性筛的for循环条件中没有j<=count是因为当j==count的时候,i==p[j],直接break了

2.3例题讲解

审题:
本题需要我们找出区间l到r之间的所有素数的个数

思路:

方法一:暴力解法

我们用双层for循环,外层for循环将l到r遍历,内层将1~根i的数与外层的数依次试除,然后若有一次成功整除则i为素数,count++。

时间上:外层为1e6,内层为1e6,所以总共的运行次数可能会达到1e12,会超时

方法二:二次筛选

我们其实不用将1到r的所有素数都选出来,而是只要遍历到根号r即可将1到r的素数筛选出来。

然后我们的筛选方法不用试除法,因为该方法要对每一个数都进行n次,所以使用埃氏筛。

第一步:将1到根号r的质数筛选出来

第二步:利用筛选出来的质数再利用埃氏筛的方法筛选出l到r之间的素数

第三步:记录区间素数个数并输出

注意:
1.筛选1到根号r的素数的目的是利用埃氏筛进行l到r的筛选

2.如何确定质数的起始倍数?
因为我们的l不是从1或2开始的,所以质数的较低倍数可能会小于l,我们为了避免这种无效筛选就要找到质数合适的起始倍数。

而该质数乘上起始倍数一定要大于等于l的值

于是我们得出以下向上取整的倍数计算式:   l+x-1/x

解析:原本l/x就可以大概得到所需倍数,但是可能因为被计算机向下取整导致计算结果仍小于l,所以我们在分子加上x-1就进行了向上取整的操作

解题:
 

#include<iostream>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
ll l, r;
int cou;//记录1~根号r的素数个数
ll prime[N];//记录素数
bool f[N];//埃氏筛筛查表
bool F[N];
void selprime()
{for (ll i = 2; i <= sqrt(r); i++){if (F[i] == true)//被筛掉了{continue;}else{prime[cou++] = i;ll j = i;while (j * i <= sqrt(r))//进行标记{F[j * i] = true;j++;}}}
}
int main()
{cin >> l >> r;l = l == 1 ? 2 : l;//控制l不为1//筛选1~根号r的素数selprime();//利用埃氏筛和选出的素数对l到r区域进行筛选for (int i = 0; i < cou; i++){ll x = prime[i];for (ll j = max(2 * x, (l + x - 1) / x * x); j <= r; j += x){f[j-l] = true;}}//记录区间素数个数ll count = 0;for (ll i = l; i <= r; i++){if (f[i-l] == false)count++;}cout << count << endl;return 0;
}

1.我们的第一次筛选使用的是埃氏筛,所以利用了一个F数组来记录筛选结果。

2.由于1既不是合数也不是素数,所以我们将l为1的情况变为l为2

3.若出现l的值和筛选出的质数的值一样的情况,也就是起始倍数为1,我们不能对它打上true标记,因为他是质数,所以我们加一个判断,若倍数为1则将倍数设置为2进行筛选

4.由于l和r的数据范围较大,为了合法记录筛选情况,我们不能直接用f[j]记录情况,而是要将j情况映射到j-l处,因为l和r之间的差小于1e6,我们是可以记录的

http://www.xdnf.cn/news/287227.html

相关文章:

  • 30天通过软考高项-第十一天
  • CodeBlocks25配置wxWidgets3.2
  • 004-nlohmann/json 快速认识-C++开源库108杰
  • 地埋式燃气泄漏检测装置与地下井室可燃气体检测装置有什么区别
  • 专业课复习笔记 4
  • Vue中的过滤器参数:灵活处理文本格式化
  • 5月5日日记
  • 基于 HTML5 Canvas 实现图片旋转与下载功能
  • linux tar命令详解。压缩格式对比
  • Java IO流核心处理方式详解
  • 论高并发下的高可用
  • LeetCode 热题 100 46. 全排列
  • 【PostgreSQL数据分析实战:从数据清洗到可视化全流程】5.1 描述性统计分析(均值/方差/分位数计算)
  • 代码随想录算法训练营Day45
  • 一个电商场景串联23种设计模式:创建型、结构型和行为型
  • Cordova开发自定义插件的方法
  • 多语言笔记系列:Polyglot Notebooks 中使用 xUnit 单元测试
  • WebAssembly(Wasm):现代Web开发的超级加速器
  • Spring Boot 之MCP Server开发全介绍
  • Linux | WEB服务器的部署及优化
  • 山东大学项目实训-创新实训-法律文书专家系统-项目报告(三)
  • 推特逆向算法,推特爬虫,数据分析,推特关键词搜索
  • C# 检查某个点是否存在于圆扇区内(Check whether a point exists in circle sector or not)
  • AI小智本地前后端部署
  • Web Workers 技术详解与最佳实践
  • Kubernetes(k8s)学习笔记(七)--KubeSphere 最小化安装
  • webpack 的工作流程
  • 备忘录模式(Memento Pattern)
  • 56.[前端开发-前端工程化]Day03-webpack构建工具
  • Windows11 VS code 安装 Cline 调用 Github MCP 配置过程坑点汇总