全网一定不是最好懂的C++线性筛素数
Part 0:概念
先给几个概念(很重要):
- 合数:如果\(xy=z\text{且}x,y\text{为正整数}\),我们就说\(x,y\text{是}z\text{的合数}\)
- 素数:如果数\(a\)的合数只有\(1,a\),则\(a\)就是一个素数
- 整除:整数\(b\)除以非零整数\(a\),商为整数,且余数为零, 我们就说\(b\)能被\(a\)整除,记做\(a | b\)。数学中,求一个数的余数的运算叫做取余,用\(a MOD b\)表示求a除以b的余数,计算机中用
%
当然,如果有\(a | b\),那么我们可以写成\(a MOD b = 0\) - 不含0,1的所有自然数除了素数就是合数
Part 1:普通筛法及优化到根号
首先,从普通筛法开始:
#include <iostream>
using namespace std;
int main(){
return 0;
}
判断一个数是不是素数,就从他的定义入手
定义是啥?回顾一下:
如果数\(a\)的合数只有\(1,a\),则\(a\)就是一个素数
再次回顾合数的概念:
如果\(xy=z\text{且}x,y\text{为正整数}\),我们就说\(x,y\text{是}z\text{的合数}\)
我们知道,不含0的所有自然数不是素数就是合数,而我们算法的名字叫做筛素数,而素数的定义离不开合数
干脆,我们把合数筛掉吧!
我们从\(xy=z入手\)。显然,\(x,y,z \ neq 0\),可以大胆的除一下:\(x = \frac{z}{y}\)
如果你比较熟悉开头的那些定义,你就会发现:因为\(x\)是个正整数,所以\(z | y\),也就是\(z MOD y = 0\)
这个地方可以好好理解一下
那么,我们开始填充代码:
#include <iostream>
using namespace std;
int main(){
bool flag = 1;//素数判断标签
int n = 10;//n代表我们需要筛从1~n的素数
for(int i = 2;i <= n;i ++){//很重要!公式中的z(也就是这里的i)要从2开始,想想为什么
//cout << "i = " << i << endl;
for(int j = 2;j < i;j ++){//很重要!想想为什么公式中的y(也就是这里的j)为什么从2开始,为什么要比i小?
//cout << "进入二层循环,j = " << j << endl;
if(i % j == 0){//如果是合数
flag = 0;
//cout << "i % j = " << i%j << endl;
//cout << "二层循环if,i = " << i << "j = " << j << endl;
break;
}
}
if(flag) cout << i << endl;//否则输出
flag = 1;//小细节~自己模拟一下
}
}
好的,代码是出来了
可以更快吗?当然可以
我举个例子:36。
36的因数有:{1,36},{2,18},{3,12},{4,9},{6,6}
把36的因数排个序:{1,2,3,4,6},{6,9,12,18,36}
以6为分界线,我们可以把它分为了两份,请注意:
\(\sqrt{36} = 6\)
哇塞!我们只需要循环到\(\sqrt{n}\)我们就可以结束了(因为因数是两两对应的(如果不考虑去重(比如49的因数表暂时定为:1,7,7,49)))
改进一下:
#include <iostream>
#include <cmath>
using namespace std;
int main(){
bool flag = 1;//素数判断标签
int n = 10;//n代表我们需要筛从1~n的素数
for(int i = 2;i <= sqrt(n);i ++){//很重要!公式中的z(也就是这里的i)要从2开始,想想为什么
cout << "i = " << i << endl;
for(int j = 2;j < i;j ++){//很重要!想想为什么公式中的y(也就是这里的j)为什么从2开始,为什么要比i小?
cout << "进入二层循环,j = " << j << endl;
if(i % j == 0){//如果是合数
flag = 0;
cout << "i % j = " << i%j << endl;
cout << "二层循环if,i = " << i << "j = " << j << endl;
break;
}
}
if(flag) cout << "------" << i << endl;//否则输出
flag = 1;
}
}
Part 2 真正的筛——埃式筛
刚才那个算法的思想再怎么说,也是个取素数
但是,判断合数显然比判断素数更简单
那么,为什么不把合数筛走,剩下的不就是素数了吗?
好办法!接下来我们想下怎么筛
其实啊,可以发现某一个不为0/1的数 × 另一个不为0/1的数 = 合数
那么,假设有一个数\(q\),我们只需要筛掉\(1q,2q,3q\text{……一直到}iq>n\)时,接着q++,继续筛!
上代码:
#include <iostream>
//代码来自https://www.cnblogs.com/Return-blog/p/12307038.html
using namespace std;
bool book[1000000];//素数标记盒子
int main(){ //求1~100以内的素数
for(int i=2;i<=100;i++)
if(!book[i]){//因为book数组在main()外部自动填充为0,所以需要!0转换为11
for(int j=i+i;j<=100;j+=i)//q,2q,3q,4q...
book[j]=1;//标记上1的就是合数了!
}
for(int i=2;i<=100;i++)
if(!book[i]) cout << i << endl;//是0就输出~
return 0;
}
哦太棒了!
但是!请你自己模拟一下这个过程,你会发现个奇怪的过程拖慢了速度!
当i = 2 => 2i=4,3i=6,4i=8...
当i = 3 => 2i=6,3i=9,4i=12...
当i = 4 => 2i=8...
Oh No!又开始重复了!
重复怎么办?再筛掉!
Part 3:线性筛
//代码来自https://www.cnblogs.com/Return-blog/p/12307038.html
#include <iostream>
using namespace std;
bool book[20005];
int prime[20005],pos,i,j;
int main(){ //求1~100以内的素数
for(i=2;i<=100;i++){
if(!book[i]) prime[++pos]=i; //判断是否被筛过。!0(没有)的话,就记录一下,
for(j=1;j<=pos;j++){
if(i*prime[j] > 100) break; //范围:不让他超过i,超过就break
book[i*prime[j]]=1; //i乘上素数得到的书就标记下,筛过了!
if(i%prime[j]==0) break; //!!!prime数组 中的素数是递增的,当 i 能整除 prime[j],那么 i*prime[j+1] 这个合数肯定被 prime[j] 乘以某个数筛掉 想想为什么
}
}
for(int i=1;i<=pos;i++)
cout << prime[pos];
return 0;
}
Oh太棒了!你终于搞到了最快的方法
最后送你个福利:证明一下为什么:当 i 能整除 prime[j],那么 i*prime[j+1] 这个合数肯定被 prime[j] 乘以某个数筛掉
首先,prime数组中的素数是递增的(这个不用说)
因为i中含有prime[j],prime[j]比prime[j+1]小
即\(i=k\times prime[j]\)
那么\(i\times prime[j+1]=(k\times prime[j])\times prime[j+1]\)
也就是\(k’\times prime[j]\)
看不懂没关系,板子背下来就好了~
全网一定不是最好懂的C++线性筛素数的更多相关文章
- BZOJ 2820 YY的GCD
AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=2820 有种方法是枚举质数然后用BZOJ2301来做但是超时了... 具体式子大概张这样: ...
- 【LOJ6052】「雅礼集训 2017 Day11」DIV(杜教筛)
点此看题面 大致题意: 求\(1\sim n\)内所有满足\(a>0\)的约数\(a+bi\)的\(a\)之和. 解题思路 首先,我们设\(x=(a+bi)(c+di)(1\le x\le n) ...
- 全网第二好懂的FFT(快速傅里叶变换)
声明:本FFT是针对OI的.专业人员请出门左拐. Ⅰ前言 很久以前,我打算学习FFT. 然而,算法导论讲的很详细,却看不懂.网上博客更别说了,什么频率之类的都来了.我暗自下了决心:写一篇人看得懂的FF ...
- 全网最详细最好懂 PyTorch CNN案例分析 识别手写数字
先来看一下这是什么任务.就是给你手写数组的图片,然后识别这是什么数字: dataset 首先先来看PyTorch的dataset类: 我已经在从零学习pytorch 第2课 Dataset类讲解了什么 ...
- [[iso教程]] 《4个月ios实体教程》全网最新、最全ios视频教程
全网最新.最全ios视频教程 内容简介 <ios实体教程>主要介绍如何使用iOS提供的强大工具集创建iOS应用.全视频对iOS操作系统做了全面的介绍,首先讲解如何构建应用程序的用户界面,涵 ...
- 传统B2B中小型企业如何做好全网营销
优网特独创全网营销服务理念,全网营销即以企业网站推广为核心,通过SEO.SEM.BBS营销.博客营销.微营销.即时通讯营销.网络口碑营销.视频营销.邮件营销.SNS营销等网络营销手段,全面提升企业网站 ...
- Linux环境PHP5.5以上连接SqlServer2008【全网最经典无错版】
原文地址:http://blog.csdn.net/21aspnet/article/details/47451253 linux版本:64位CentOS 6.4 Nginx版本:nginx1.8.0 ...
- Android 代理服务器为全网提供代理
Android 代理服务器为全网提供代理 背景:学校WiFI过滤较严,故学生很少有可以上网账号的.而学校又分为俩层验证,第一层可以注册并且拥有访问校内网的权限,第二层为校内密码验证机(非服务器)进行用 ...
- 教你开启红米的USB大容量存储选项,全网首发哦
教你开启红米的USB大容量存储选项,全网首发哦 http://bbs.7to.cn/thread-10732-1-1.html 发表于 2014-4-29 110643 红米note入手也有两天了.各 ...
随机推荐
- 让VS2019支持.NET Core WinForms和WPF设计器的临时办法(转)
.net core 3.0 Preview 6,vs2019 16.1.4 暂时还不支持Winform designer 解决方案: 将Form1修改为其他名字 WinForm 编辑项目文件 加 ...
- Java多线程的创建(一)
方法一:继承Thread类实现 1.创建一个类A,并继承Thread类 2.重写A的run()方法 3.创建A的实例对象b,即创建了线程对象 4.使用b调用start()方法:启动线程(会自动调用ru ...
- Java入门 - 高级教程 - 09.文档注释
原文地址:http://www.work100.net/training/java-documentation.html 更多教程:光束云 - 免费课程 文档注释 序号 文内章节 视频 1 概述 2 ...
- 基于javaSwing的贪食蛇游戏
这个项目时,是我好几年前写的了.但对刚入门,或者想瞧瞧java的图形的界面swing的同学,还是有点用处的. 在这推荐给你. 涉及技术点 swing,多线程,文件读写,多媒体文件播放等 游戏简介 该游 ...
- windows10卸载虚拟机忘记按照步骤卸载的实际问题
好久没有写博客了,由于太多事情,工作需要用到虚拟机,结果,虚拟机出问题,,,怎么办???我的办法就是卸载了重新安装一个,结果呢?太心急没有按照不知操作,今天弄了一下午,终于弄好了... 错误原因,用了 ...
- axios用post传参,后端无法获取参数问题
最近用vue+nodejs写项目,前端使用axios向后台传参,发现后台接收不到参数. 后台是node+express框架,然后使用了body-parser包接收参数,配置如下: const expr ...
- Python学习,第七课 - 文件操作
Python中对文件的相关操作详解 文件的操作在今后的Python开发中也是使用非常频繁的. 先说下对文件操作的流程 打开文件,得到文件的一个句柄,赋值给一个变量 然后通过句柄对文件进行操作(内容的增 ...
- Visual studio 2015 与 mysql 连接
Visual Studio 2015 Community连接到MySQL,步骤很简单,但刚弄的时候一脸. 这个学期开了一门课程,老师教的是visual studio 2010来开发.net的,但是我自 ...
- 基于Flask框架搭建视频网站的学习日志(三)之原始web表单
基于Flask框架搭建视频网站的学习日志(三)1.原始Web 表单 本节主要用于体验一下前端后端直接数据的交互,样例不是太完善,下一节会加入Flash处理,稍微完善一下页面 (备注:建议先阅读廖雪峰老 ...
- 机器学习-计算机视觉和卷积网络CNN
概述 对于计算机视觉的应用现在是非常广泛的,但是它背后的原理其实非常简单,就是将每一个像素的值pixel输入到一个DNN中,然后让这个神经网络去学习这个模型,最后去应用这个模型就可以了.听起来是不是很 ...