信息学奥赛一本通 2040:【例5.7】筛选法找质数 (普通筛 线性筛)

【题目链接】

ybt 2040:【例5.7】筛选法找质数

【题目考点】

1. 普通筛法找质数(埃拉托色尼筛法)

基本思想:把从2到N的一组正整数从小到大按顺序排列。从中依次删除2的倍数、3的倍数、5的倍数,直到 N \sqrt{N} N 的倍数为止,剩余的即为2~N之间的所有质数。
普通筛法复杂度为: O ( n l o g l o g n ) O(nloglogn) O(nloglogn)

为什么直到 N \sqrt{N} N 的倍数为止,以下是证明:

  • 命题:对于整数n满足 N < n ≤ N \sqrt{N} < n \le N N <nN,n是质数,或n是合数时,存在一个整数a满足 a ≤ N a \le \sqrt{N} aN ,a是n的因数。
  • 证明:显然n可以是质数。如果n是合数,使用反证法。
  • 假设n是合数时,n的因数(除了1)都大于 N \sqrt{N} N 。由于n是合数,n至少有两个不为1或n的因数,设为a,b,且存在等式 a ⋅ b ⋅ x = n a\cdot b\cdot x = n abx=n,其中x是某个正整数。
    a > N , b > N ⇒ a ⋅ b > N ≥ n a>\sqrt{N}, b>\sqrt{N} \Rightarrow a\cdot b >N\ge n a>N ,b>N ab>Nn,即 a ⋅ b > n a\cdot b > n ab>n
    根据 a ⋅ b ⋅ x = n a\cdot b\cdot x = n abx=n,有 a ⋅ b ≤ n a\cdot b \le n abn
    出现矛盾,因而假设不成立,原命题得证。
2. 线性筛(欧拉筛)

基本思想:让每个合数只通过它最小的质因数被筛掉。

例:
8的最小质因数是2,那么让合数8通过质数2被筛掉。
15的最小质因数是3,那么让合数15通过质数3被筛掉。

普通筛法中,数字可能会被多次筛掉,线性筛中,每个数字只被筛掉一次。
线性筛法复杂度为: O ( n ) O(n) O(n)
线性筛的字面意思就是该算法的复杂度为线性的。

例:对于合数15
在普通筛法中:看质数3的倍数时,15被筛掉一次,看质数5的倍数时,15被筛掉一次。
在线性筛中: 15只会通过质数3被筛掉一次。

线性筛法中,维护一个质数数组prime[]。
算法如下:

  • 每次循环观察一个数字i
  • 如果i是质数,加入质数数组
  • 无论i是不是质数,都顺序遍历质数数组,取到的质数为prime[j]
    • 只要i不是prime[j]的倍数,那么prime[j]就是i*prime[j]的最小质因数。i*prime[j]这个合数通过prime[j]这个最小质因数被筛掉了。
    • 如果i是prime[j]的倍数,则跳出循环

原理解释如下:

  • 由于prime数组中的质数是递增的,在遍历prime数组的过程中,只要没有遇到i是prime[j]的倍数的情况,那么prime[j]一定是i*prime[j]的最小质因数。当第一次遇到i是prime[j]的倍数的情况时,prime[j]仍然是i*prime[j]的最小质因数。
  • 如果继续看prime[j]后面第x个质数(x大于等于1),此时要判断数字Y=i*prime[j+x]是否要被筛掉。由于prime[j+x] > prime[j],i已经具有更小的质因数prime[j],所以数字Y的最小质因数是prime[j]而不是prime[j+x],所以Y这个数不应该在i为这个值时被筛掉,它应该在i为Y/prime[j]时被筛掉。

例:假设i是25。25不是2的倍数,那么2是2*25的最小质因数,25也不是3的倍数,那么3是3*25的最小质因数,25是5的倍数,5仍然是5*25的最小质因数。
25不是7的倍数,7不是7*25的最小质因数了。7*25=175被筛掉的时机是:当i循环到35时,175=35*5,5是175的最小质因数,175通过质数5被筛掉。

【题解代码】

解法1:普通筛法(埃拉托色尼筛法)
#include<bits/stdc++.h>
using namespace std;
int main()
{
    bool isPrime[1005] = {};//isPrime[i]:i是否是质数 
    int n;
    cin >> n;
    for(int i = 2; i <= n; ++i)//初值状态下,把每个数字都标记为质数 
        isPrime[i] = true;//isPrime[0],与isPrime[1]都为false,0,1都不是质数 
    for(int i = 2; i <= int(sqrt(n)); ++i)
    {
        if(isPrime[i])
        {
            for(int j = 2*i; j <= n; j += i)//如果一个数字是某质数的倍数,那么把它标记为合数 
                isPrime[j] = false;
        }
    }
    for(int i = 2; i <= n; ++i)
    {
        if(isPrime[i])
            cout << i << endl;
    }
    return 0;
}
解法2:线性筛(欧拉筛)
#include<bits/stdc++.h>
using namespace std;
int main()
{
    bool isPrime[1005] = {};//isPrime[i]:i是否是质数 
    int n, prime[1005], psize = 0;//prime:质数数组 psize:prime数组长度 
    cin >> n;
    for(int i = 2; i <= n; ++i)//初值状态下,把每个数字都标记为质数 
        isPrime[i] = true;//isPrime[0],与isPrime[1]都为false,0,1都不是质数 
    for(int i = 2; i <= n; ++i)
    {
        if(isPrime[i])//如果i是质数 
            prime[++psize] = i;//将i填充到指数数组prime 
        for(int j = 1; j <= psize && i*prime[j] <= n; ++j)//遍历质数数组 
        {//筛去当前观察的数i与某质数prime[j]的乘积
            isPrime[i*prime[j]] = false;//i*prime[j]这个数通过prime[j]筛掉 
            if(i%prime[j] == 0)
                break;
        }
    }
    for(int i = 2; i <= n; ++i)
    {
        if(isPrime[i])
            cout << i << endl;
    }
    return 0;
}
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值