质数筛法详解

理论及实现

定义:

若一个正整数无法被除了1和它本身的之外的任何自然数整除,则称该为质数(素数),否则称该正整数为合数。


判定方法

试除法

引理:

若一个正整数\(N\)为合数,则存在一个能整除\(N\)的数\(T\)且\(2≤T≤ \sqrt N\)

证明就不再赘述,读者可以自行验证:

因此,我们只需要枚举\(2-\sqrt N\)。只要这之中的所有数都不能被\(N\)整除,那么\(N\)就是质数了:

    #include<cmath>

    bool is_prime(int n) {
        if(n < 2) return false;
        for(int i = 2; i <= sqrt(n); i++)
            if(n % i == 0) return false;
        return true;
    }

    //调用 (自定义方式)
    if(is_prime(x))...; 

    bool flag = is_prime(x); 

Eratosthenes 筛法

基本思想:

任意整数 x 的倍数 2x、3x…,都不是质数。

根据质数的定义,上述命题显然成立。

那么我们可以从\(2\)开始,由小到大扫描每个数\(x\),把它的倍数(\(2x\),\(3x…\) \(N/x\)(向下取整)\(*x\) )全部标记为合数。 若扫描到一个数时,它尚未被标记,则它一定是质数。

并且\(Eratosthenes\) 筛法还有可以优化的地方,比如2与3都会标记到6。其实我们可以发现,小于\(x^2\)的数其实都已经被其他书标记过了,因此我们可以优化,每次从\(x^2\)开始扫描即可。

    #include<cstring>
    #include<cstdio>

    bool v[SIZE];
    void primes(int n) {//n为范围
        memset(v,0,sizeof(v));
        for(int i = 2; i <= n; i++) {
            if(v[i]) continue;
            printf("%d", i);
            for(int j = i; j <= n/i; j++)
                v[i * j] = 1;
        }
    }
    //调用:
    primes(n);

\(Eratosthenes\) 筛法的时间复杂度为\(O(N\log \log N)\),已经非常接近线性。


线性筛法

即使在\(Eratosthenes\) 筛法优化后也会重复标记,这样就造成了时间的浪费,比如说\(2\)和\(3\)都会标记\(12\)。

线性筛法思想:

通过“从大到小累积质因子”的方式标记合数,即12只有322一种标记方式。

对于下面两步有两种办法

  • 第一种:
  1. 依次考虑\(2-N\)之间的每一个数\(i\)。
  2. 若\(v[i]=0\),说明\(i\)是质数,把\(i\)保存下来。
  3. 扫描不大于\(v[i]\)的每个质数\(p\),令\(v[i*p]\)=1。
  4. 在扫描过程中,记录筛过的质数总数为\(num\),且要保证\(prime[j],j≤num\)为合数\(i*prime[j]\)的最小质因子,在找到一个\(i\)满足\(i\) % \(prime[j]=0\),即比一个合数大的质数和该合数的乘积可用一个更大的合数和比其小的质数相乘得到,那么就退出标记。
  • 第二种(《算法竞赛进阶指南》)
  1. 依次考虑\(2-N\)之间的每一个数\(i\)。
  2. 若\(v[i]=x\),说明\(x\)是质数,把\(x\)保存下来。
  3. 扫描不大于\(v[i]\)的每个质数\(p\),令\(v[i*p]=p\)。也就是在\(i\)的基础上累积一个质因子\(p\)。因为\(p≤v[i]\),所以\(p\)就是合数\(i*p\)的最小质因子。

以上两种方法都保证合数\(i*p\)只会被它最小的质因子筛一次,时间复杂度为\(O(N)\),已经达到了线性。

   //第一种方法:
   #include<cstring>
   #include<cstdio>

   int v[MAX_N], prime[MAX_N], num;
   void primes(int n) {
       memset(v, 0, sizeof(v));
       num = 0;
       for(int i = 2; i <= n; i++) {
           if(v[i] == 0) prime[++num] = i;
           for(int j = 1; j <= num && i * prime[j] <= n; j++) {
               v[i * prime[j]] = 1;
               if(i % prime[j] == 0) break;
           }
       }
   }
   //调用:看自己使用方式,下面为判定质数
   primes(n);

   if(v[x]) printf("NO");
   else printf("YES"); 

   //第二种方法
   #include<cstring>
   #include<cstdio>

   int v[MAX_N], prime[MAX_N], num;
   void primes(int n) {
       memset(v, 0, sizeof(v));
       num = 0;
       for(int i = 2; i <= n; i++) {
           if(v[i] == 0) v[i] = i, prime[++num] = i;
           for(int j = 1; j <= num; j++) {
               if(prime[j] > v[i] || prime[j] > n / i) break;
               v[i * prime[j]] = prime[j];
           }
       }
   }
   //调用:看自己使用方式,下面为判定质数
   primes(n);

   if(v[x] == x) printf("YES");
   else printf("NO"); 

“6倍数”筛法

当然,质数筛法还不止这些,我在网上查阅到了这种“6筛法”。

  • 引理

大于等于5的质数都分布在6的倍数的两侧。

但需要强调的是,分布在\(6\)的倍数的两侧的数不一定是质数:如 \(4*6=24\),但\(25\)不是质数。

  • 证明(反证法):

令\(x≥1,x∈Z\),将大于等于\(5\)的自然数表示为

\(6x-1,6x,6x+1,6x+2…6(x+1),6(x+1)+1…\)

若大于等于\(5\)质数\(p\)不在\(6\)的倍数的两侧,即\(p\)不能表示为\(6i-1\)或\(6i+1,i=1,2,3…\)那么质数就一定表示为\(6i+2\)、\(6i+3\)、\(6i+4,i=1,2,3…\)其中的一种,但是很明显的\(6i+2\)和
\(6i+4\)为\(2\)的倍数,\(6i+3\)为3的倍数,根据质数定义,则\(p\)一定不是质数,与假设矛盾,故原命题成立。



对于“试除法”,我们根据上面的定理可以将 \(i++\) 改为\(i+=6\),并且\(i\)的初值设为\(5\)。每次判定 一个数\(x\)是否能整除 \(i\)或是\(i+2,2≤i≤sqrt(n),i=1,2,3…\),若是,则该数不是质数;否则,运行完后,没有返回f\(alse\),则该数是质数,返回\(true\)。

  • 这里对为什么是整除\(i,i+2\)做一个说明:\(i\)初值从\(5\)开始,每次加\(6\),始终是\(6\)的倍数左侧的那个数,即\(i=6x-1,x=1,2,3…\),所以每次对\(i\)和\(i+2\)判定。
    #include<cmath>

    bool is_prime(int x) {
        if(x == 1 || x == 4) return false;
        if(x == 2 || x == 3) return true;
        //以上对小于5的数进行处理
        if(x % 6 != 1 || x % 6 != 5) return false;
        //不在6的倍数的两侧(不能用6x-1或6x+1表示)
        for(int i = 5; i <= sqrt(x); i += 6)
            if(x % i == 0 || x % (i + 2) == 0)
                return false;
        //在6两侧的不一定是质数,用6x-1和6x+1判定即可
        //类似于用质数标记合数的思想
        return true;
    }
    //调用:(判定质数)
    if(is_prime(x)) printf("YES");
    else printf("NO"); 

    //记录质数(O(N*sqrt(N)/6)
    int num, v[MAX_N];
    if(is_prime(i)) v[++num] = i//i = 1, 2 ... N

*章末练习

  • 以下题目按难度排序(带“+”题目为选做题目):
  1. P3383 【模板】线性筛素数
  2. P1865 A % B Problem
  3. P2563 [AHOI2001]质数和分解
  4. P2926 [USACO08DEC]拍头Patting Heads
  5. P1313 计算系数
  6. P1069 细胞分裂
  7. P3737 [HAOI2014]遥感监测
  8. +P2044 [NOI2012]随机数生成器
  9. P2455 [SDOI2006]线性方程组
  10. +P2924 [USACO08DEC]大栅栏Largest Fence
  11. P4446 [AHOI2018初中组]根式化简
  12. P3200 [HNOI2009]有趣的数列
  13. P2568 GCD
  14. +P4208 [JSOI2008]最小生成树计数
  15. +P2485 [SDOI2011]计算器
  16. +P3702 [SDOI2017]序列计数
  17. +P4068 [SDOI2016]数字配对
  18. +P4359 [CQOI2016]伪光滑数
  19. P3260 [JLOI2014]镜面通道
  20. +P2150 [NOI2015]寿司晚宴
  21. +P3747 [六省联考2017]相逢是问候
  22. +P4191 [CTSC2010]性能优化
  23. P4607 [SDOI2018]反回文串
  • 以上较难的题目可能需要的知识储备:

乘法逆元,逆元、组合数学、卡特兰、矩阵运算、高斯消元、生成树、左偏树、哈希、容斥、快速傅里叶变,DFT,FFT、前缀和、块状链表,块状数组,分块、同余,中国剩余定理、动态规划,动规,dp、状态压缩、分治、线段树,以及一系列其他的基本操作(如搜索)、数论知识(如\(gcd\)质因数分解)


写在后面

下面为题目P3383 【模板】线性筛素数的一些评测记录(无(\(O_2\))):

  1. 朴素算法,时间复杂度\(O(sqrt(N)M),AC:\)朴素算法
  2. “6倍数”优化朴素算法,时间复杂度\(O(sqprt(N)M/6),AC:\)“6倍数”筛法
  3. \(Eratosthenes\) 筛法,时间复杂度\(O(NloglogN),AC:\)Eratosthenes 筛法
  4. 线性筛,时间复杂度\(O(N),AC:\)线性筛1&线性筛2
  • \(PS:\)
  1. 网络波动可能导致评测结果不够准确,以理论时空复杂度为准。
  2. 由于测试数据\(N<=10000000,M<=100000\),\(M\)远小于\(N\),导致朴素算法和优化的“6倍数”筛法看起来比 \(Eratosthenes\) 筛法和线性筛法快,实际上是后者在大数据上远快于前者。
  3. 部分思想及材料参考李煜东《算法竞赛进阶指南》
  4. "6倍数"筛法原文来自于:判断一个数是否为质数/素数——从普通判断算法到高效判断算法思路

原文地址:https://www.cnblogs.com/Ning-H/p/11566196.html

时间: 2024-10-08 20:50:04

质数筛法详解的相关文章

RSA公钥算法详解

1970年左右科学家们开始思考公钥加密系统的可能性.经过科学家多年的研究,终于在1977年时,来自MIT的Ron Rivest,AdiShamir和Leonard Adlemn三个人合写了一篇论文,给出了至今仍然安全的公钥加密算法,即以三个人姓氏的首字母命名的RSA算法. RSA的过程步骤如下: 1)       找两个大质素p和q 2)       设n=p·q 算出m=(p-1)(q-1) 3)       找出e和d,使得 e·d mod = 1 (随便找出一个与m互质的数e,求解方程e·

安全体系(二)——RSA算法详解

本文主要讲述RSA算法使用的基本数学知识.秘钥的计算过程以及加密和解密的过程. 安全体系(零)—— 加解密算法.消息摘要.消息认证技术.数字签名与公钥证书 安全体系(一)—— DES算法详解 1.概述 RSA公钥加密算法是1977年由罗纳德·李维斯特(Ron Rivest).阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的.1987年首次公布,当时他们三人都在麻省理工学院工作.RSA算法以他们三人姓氏开头字母命名. RSA是目前最有影响力的公钥加密

(转)详解八大排序算法

概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 我们这里说说八大排序就是内部排序. 当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序.堆排序或归并排序序. 快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短: 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 将一个记录插入到

hashtable详解

在 红黑树详解 文章中,二叉搜索树具有对数平均时间的表现是构造在这样的假设下的:输入数据有足够的随机性. 本篇介绍的hashtable(散列表)的数据结构,在插入.删除.搜寻等操作上也具有“常数平均时间”的表现,而且这种表现是以统计数据为基础,不需仰赖输入元素的随机性. 1. hashtable 概述 hashtable 可提供对任何有名项的存取和删除操作.由于操作对象是有名项,所以hashtable也可被视为一种字典结构.这种结构尝试提供常数时间之基本操作,如: 要存取所有的16-bits且不

Androidhttp请求加密机制详解

Android开发中,难免会遇到需要加解密一些数据内容存到本地文件.或者通过网络传输到其他服务器和设备的问题,但并不是使用了加密就绝对安全了,如果加密函数使用不正确,加密数据很容易受到逆向破解攻击.还有很多开发者没有意识到的加密算法的问题. 1.需要了解的基本概念 密码学的三大作用:加密( Encryption).认证(Authentication),鉴定(Identification) 加密:防止坏人获取你的数据. 鉴权:防止坏人假冒你的身份. 明文.密文.密钥.对称加密算法.非对称加密算法,

BSGS算法_Baby steps giant steps算法(无扩展)最强详解,你从未见过的详细

Baby Steps-Varsity Giant Step-Astronauts(May'n?椎名慶治) 阅读时可以听听这两首歌,加深对这个算法的理解.(Baby steps少女时代翻唱过,这个原唱反而不是很有名……Giant Step就比较碉,是一个假面骑士片的插曲,由超碉的May'n和一个人建立的临时组合唱的,怕不怕) 这个主要是用来解决这个题: A^x=B(mod C)(C是质数),都是整数,已知A.B.C求x. 我在网上看了好多介绍,觉得他们写得都不够碉,我看不懂…于是我也来写一发. 先

线段树详解 (原理,实现与应用)

线段树详解 By 岩之痕 目录: 一:综述 二:原理 三:递归实现 四:非递归原理 五:非递归实现 六:线段树解题模型 七:扫描线 八:可持久化 (主席树) 九:练习题 一:综述 假设有编号从1到n的n个点,每个点都存了一些信息,用[L,R]表示下标从L到R的这些点. 线段树的用处就是,对编号连续的一些点进行修改或者统计操作,修改和统计的复杂度都是O(log2(n)). 线段树的原理,就是,将[1,n]分解成若干特定的子区间(数量不超过4*n),然后,将每个区间[L,R]都分解为 少量特定的子区

Java集合详解3:Iterator,fail-fast机制与比较器

Java集合详解3:Iterator,fail-fast机制与比较器 今天我们来探索一下LIterator,fail-fast机制与比较器的源码. 具体代码在我的GitHub中可以找到 https://github.com/h2pl/MyTech 喜欢的话麻烦star一下哈 文章首发于我的个人博客: https://h2pl.github.io/2018/05/9/collection3 更多关于Java后端学习的内容请到我的CSDN博客上查看:https://blog.csdn.net/a72

linux sysbench: CPU性能测试详解

网上sysbench教材众多,但没有一篇中文教材对cpu测试参数和结果进行详解.本文旨在能够让读者对sysbench的cpu有一定了解. 小慢哥的原创文章,欢迎转载 1.sysbench基础知识 sysbench的cpu测试是在指定时间内,循环进行素数计算 素数(也叫质数)就是从1开始的自然数中,无法被整除的数,比如2.3.5.7.11.13.17等.编程公式:对正整数n,如果用2到根号n之间的所有整数去除,均无法整除,则n为素数. 2.sysbench安装 # CentOS7下可使用yum安装