数论线性筛求质数

线性筛求质数

埃氏筛的缺陷:

对于一个数,埃氏筛会把6在2×3和3×2都删一次,导致时间复杂度达到了 O ( n l o g l o g n ) O(nloglogn) O(nloglogn),线性筛就是针对这个重复删除进行了优化,使得时间复杂度达到了线性的复杂度。


线性筛的优化过程:

显然,一个数(1除外)的倍数肯定是合数,这是我们从埃氏筛中就用的方法。

具体解释一下循环中的判断语句:
1.如果这个数是个质数,那么就加入到质数集合primenum[]中
2.如果这个数是个合数,根据唯一分解定理 X = p 1 k 1 p 2 k 2 p 3 k 3 … … , X=p_1^{k_1}p_2^{k_2}p_3^{k_3}……, X=p1k1p2k2p3k3其中 p i p_i pi都按升序排列
那么如果X一旦碰到了自己的一个质因子,即(X%primenum[j]==0),首先说明这个数肯定不是质数,其次是接下来的 i ∗ p r i m e n u m [ j + 1 ] i*primenum[j+1] iprimenum[j+1]不用接着筛了,直接break,具体看证明:


个人证明:

步骤0:首先要明确 p r i m e n u m [ ] 是 递 增 的 primenum[]是递增的 primenum[]

步骤1:当一个数是质数的时候,就会加入到质数集合

步骤2:当一个数是合数的时候,什么时候要对它的倍数进行筛,什么时候不用呢?这就变成了最大的问题了!!观察分解后的式子,如果 i % 最 小 质 因 子 = = 0 i\%最小质因子==0 i%==0,即说明这个数字肯定会被后续筛除,因为这个在maxn范围内最小质因子的所有倍数最终都会筛除,这个时候就需要用到步骤0了!!具体看式子:
i % p r i m e n u m [ j ] = = 0 i\%primenum[j]==0 i%primenum[j]==0==>设 i = k ∗ p r i m e n u m [ j ] i=k*primenum[j] i=kprimenum[j];
i ∗ p r i m e n u m [ j + 1 ] = k ∗ p r i m e n u m [ j ] ∗ p r i m e n u m [ j + 1 ] = k 1 ∗ p r i m e n u m [ j ] i*primenum[j+1]=k*primenum[j]*primenum[j+1]\\=k_1*primenum[j] iprimenum[j+1]=kprimenum[j]primenum[j+1]=k1primenum[j],也就是说在之后某个数字 k 1 k_1 k1乘上primenum[j]会筛选掉i×primenum[j+1]这个数字,如果之前筛了那就重复了


大神的图片解释:

参考:https://www.jianshu.com/p/f16d318efe9b在这里插入图片描述
追究其源头的话,其实就是通过最小质因子达到筛选过程,例如6的最小质因子是2,那么就不必在3×2的地方进行操作了,这样的一个过程就是线性复杂度的


具体代码(板子:

时间复杂度: O ( n ) O(n) O(n)

bool prime[maxn];
int m=1;
int primenum[maxn];
void prime_init()
{
    memset(prime,true,sizeof prime);
    prime[0]=prime[1]=false;
    rep(i,2,maxn)
    {
        if (prime[i])primenum[m++]=i;
        for (int j=1;j<m&&i*primenum[j]<=maxn;j++)
        {
            prime[i*primenum[j]]=false;
            if (i%primenum[j]==0)break;
        } 
    }
}

例题1
HDU - 2161
题意:1和2不算质数,给出一组数据,判断是不是质数
做法:用线性筛的话,得往质数集里面添加一个2,尽管2在这个问题里不算质数,但是质数集里仍需要,因为考虑到4会被遗漏掉

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<queue>
#include<map>
#define ll long long
#define pb push_back
#define rep(x,a,b) for (int x=a;x<=b;x++)
#define repp(x,a,b) for (int x=a;x<b;x++)
#define W(x) printf("%d\n",x)
#define WW(x) printf("%lld\n",x)
#define pi 3.14159265358979323846
#define mem(a,x) memset(a,x,sizeof a)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
using namespace std;
const int maxn=2e4+7;
const int INF=1e9;
const ll INFF=1e18;
int prime[maxn];
int primenum[maxn];
void prime_init()
{
    int m=1;
    rep(i,3,maxn)prime[i]=1;
    prime[0]=prime[1]=prime[2]=0;
    primenum[0]=2;
    rep(i,2,maxn)
    {
        if (prime[i])
        {
            primenum[m++]=i;
        }
        for (int j=0;j<m&&i*primenum[j]<maxn;j++)
        {
            prime[i*primenum[j]]=0;
            if (i%primenum[j]==0)break;
        }
    }
}
int main()
{
    int n,K=0;
    prime_init();
    while(~scanf("%d",&n)&&n>0)
    {
        if (prime[n])printf("%d: yes\n",++K);
        else printf("%d: no\n",++K);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值