线性筛素数

想要快速地筛出一定上限内的素数?

下面这种方法可以保证范围内的每个合数都被删掉(在 bool 数组里面标记为非素数),而且任一合数只被:

“最小质因数 × 最大因数(非自己) = 这个合数”

的途径删掉。由于每个数只被筛一次,时间复杂度为 O(n)O(n)O(n)。

欧拉筛

先浏览如何实现再讲其中的原理。


实现

bool isPrime[1000001];
//isPrime[i] == 1表示:i是素数
int Prime[1000001], cnt = 0;
//Prime存质数

void GetPrime(int n)//筛到n
{
    memset(isPrime, 1, sizeof(isPrime));
    //以“全都是素数”为初始状态,逐个删去
    isPrime[1] = 0;//1不是素数

    for(int i = 2; i <= n; i++)
    {
        if(isPrime[i])//没筛掉 
            Prime[++cnt] = i; //i成为下一个素数

        for(int j = 1; j <= cnt && i*Prime[j] <= n/*不超上限*/; j++) 
        {
            //从Prime[1],即最小质数2开始,逐个枚举已知的质数,并期望Prime[j]是(i*Prime[j])的最小质因数
            //当然,i肯定比Prime[j]大,因为Prime[j]是在i之前得出的
            isPrime[ i*Prime[j] ] = 0;

            if(i % Prime[j] == 0)//i中也含有Prime[j]这个因子
                break; //重要,见原理
        }
    }
}

原理概述

(1)一个数的最小因数(非1)当然是一个质数。毕竟如果是合数的话可以再分。

(2)代码中 PrimePrimePrime 数组中存的质数是递增的。

(3)代码中的 i×Prime[j]i×Prime[j]i×Prime[j] 是尝试筛掉的某个合数,其中,我们期望 Prime[j]Prime[j]Prime[j] 是这个合数的最小质因数 (1)(1)(1)。它是怎么得到保证的?

jjj 的循环中,有一句就做到了这一点:

            if(i % Prime[j] == 0)
                break; 

jjj 的循环到这里停止的理由是:

  • iii 的最小质因数是 Prime[j]Prime[j]Prime[j](否则更早break)。

  • i×Prime[<j]i × Prime[<j]i×Prime[<j] 的最小质因数是 Prime[<j]Prime[<j]Prime[<j]。符合筛条件 (1)(1)(1)。

  • i×Prime[>j]i × Prime[>j]i×Prime[>j] 的最小质因数是 Prime[j]Prime[j]Prime[j]。不符筛条件 (1)(1)(1)。

(如果你立即理解了上面这三点,并且发现任何合数在整个流程中一定被最小质因数筛掉(“那个” iii 内枚举质数不会提前break),那么后面都不用看了)

可以具体说成是:

①:如果 imodPrime[j]==0i \mod Prime[j] == 0imodPrime[j]==0,说明 iii 有 Prime[j]Prime[j]Prime[j] 这个因子。那么 iii 可以表示为 Prime[j]×kPrime[j] × kPrime[j]×k。

②:如果我们再往下,下一个被筛掉的合数将是 i×Prime[j+1]i × Prime[j+1]i×Prime[j+1](此时我们期望 Prime[j+1]Prime[j+1]Prime[j+1] 是这个合数的最小质因数),

③:根据①,该合数可以表示为 (Prime[j]×k)×Prime[j+1](Prime[j] × k) × Prime[j+1](Prime[j]×k)×Prime[j+1]。

④:看, Prime[j]Prime[j]Prime[j] 明显就是该合数的一个更小的质因数,说明该合数的最小质因数并不是 Prime[j+1]Prime[j+1]Prime[j+1],那么用 Prime[j+1]Prime[j+1]Prime[j+1] 去筛这个合数是不符合要求的。所以不应该在这里筛掉。

⑤:后面 i∗Prime[j+2]i * Prime[j+2]iPrime[j+2] 等等与此相同,他们都一定有更小的质因数 Prime[j]Prime[j]Prime[j],就不要用 Prime[j+2]Prime[j+2]Prime[j+2] 这个更大的质因数去筛。

注意,imodPrime[j]==0i \mod Prime[j] == 0imodPrime[j]==0 并不影响 Prime[j]Prime[j]Prime[j] 是 i×Prime[j]i×Prime[j]i×Prime[j] 这个数的最小质因数。

小提示:

iii 还不大的时候,可能会一层内就筛去大量质数,看上去耗时比较大,但是由于保证了筛去的质数日后将不会再被筛,复杂度是线性的。到 iii 接近 nnn 时,每层几乎都不用做什么事。

建议看下面两个并不烦的证明,便于信任这个筛法和以后扩展学习。

正确性(所有合数都会被标记)证明

设一合数 CCC 的最小质因数是 p1p_1p1,设 B=C/p1B = C / p_1B=C/p1C=B×p1C = B × p_1C=B×p1),则 BBB 的最小质因数不小于 p1p_1p1(否则 CCC 也有这个更小因子)。那么当枚举到 i=Bi = Bi=B 时,我们会从小到大枚举各个质数,因为 i=Bi = Bi=B 的最小质因数不小于 p1p_1p1,所以 iii 在质数枚举至 p1p_1p1 之前都不会break,因而就这次而言 CCC 一定会被 B×piB × p_iB×pi 删去。

核心:亲爱的 BBB 的最小质因数必不小于 p1p_1p1

例:315=3×3×5×7315 = 3 × 3 × 5 × 7315=3×3×5×7,其最小质因数是 333。考虑 i=315/3=105i = 315 / 3 = 105i=315/3=105 时,我们从小到大逐个枚举质数,正是因为 iii 的最小质因数不会小于 333(本例中就是 333),所以当枚举 j=1(Prime[j]=2)j = 1 (Prime[j] = 2)j=1(Prime[j]=2) 时,iii 不包含 222 这个因子,也就不会break,直到 Prime[j]=3Prime[j] = 3Prime[j]=3 之后才退出。

当然质数不能表示成“大于1的某数×质数”,所以整个流程中不会标记。

线性复杂度证明

注意这个算法一直使用“某数×质数”去筛合数,又已经证明一个合数一定会被它的最小质因数 p1p_1p1 筛掉,所以我们唯一要担心的就是同一个合数是否会被“另外某数 × p1p_1p1 以外的质数”再筛一次导致浪费时间。设要筛的合数是 CCC,设这么一个作孽的质数为 pxp_xpx,再设 A=C/pxA = C / p_xA=C/px,则 AAA 中有 p1p_1p1 这个因子。当 i=Ai = Ai=A 想要重筛 CCC,却在枚举 Prime[j]=p1Prime[j] = p_1Prime[j]=p1 时因为 imodPrime[j]==0i \mod Prime[j] == 0imodPrime[j]==0 就退出了。因而 CCC 的任何 p1p_1p1​ 外的质因数都不能筛它。

核心:罪恶的 AAA 中必有 p1p_1p1 这个因子。

例:315=3×3×5×7315 = 3 × 3 × 5 × 7315=3×3×5×7。首先,虽然看上去有两个 333,但我们筛数的唯一一句话就是

            isPrime[ i*Prime[j] ] = 0;

所以,315315315 只可能用 105×3105 × 3105×3 或 63×563 × 563×5 或 45×745 × 745×7 这三次筛而非四次。然后,非常抱歉,后两个 i=63,i=45i = 63, i = 45i=63,i=45 都因为贪婪地要求对象为 555 或 777 而被迫拥有 333 这个因数,因此他们根本枚举不到对象就break了。

以上两个一证,也就无可多说了。

这是我的代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
bool isPrime[10000001];
int Prime[10000001],c=0,n;
inline void p()
{
memset(isPrime,1,sizeof(isPrime));
    isPrime[1]=0;    
    for(register int i=2;i<=n;i++)
    {
        if(isPrime[i])
            Prime[++c]=i;
        for(register int j=1;j<=c&&i*Prime[j]<=n;j++)
        {
            isPrime[i*Prime[j]]=0;
            if(i%Prime[j]==0)break;
        }
    }    
}
int main()
{
    int m,x;
    bool pd;
    scanf("%d%d",&n,&m);
    p();
    while(m--)
    {
        pd=0;
        scanf("%d",&x);
        for(register int i=1;i<=n;i++)
        {
        if(Prime[i]==x)
        {
            pd=1;
            break;
        }
        }
        pd==1?printf("Yes\n"):printf("No\n");
    }
    return 0;
}

你以为就AC了吗,天真!!!

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
bool isPrime[10000001];
int Prime[10000001],c=0,n;
inline void p()
{
    memset(isPrime,1,sizeof(isPrime));
    isPrime[1]=0;    
    for(register int i=2;i<=n;i++)
    {
        if(isPrime[i])
            Prime[++c]=i;
        for(register int j=1;j<=c&&i*Prime[j]<=n;j++)
        {
            isPrime[i*Prime[j]]=0;
            if(i%Prime[j]==0)break;
        }
    }
}
int main()
{
    int m,x;
    scanf("%d%d",&n,&m);
    p();
    while(m--)
    {
        scanf("%d",&x);
        isPrime[x]==1?printf("Yes\n"):printf("No\n");
    }
    return 0;
}

 

具体差别自己看

 

posted @ 2018-11-07 21:18  JCRL  阅读(294)  评论(0编辑  收藏  举报