素数筛选及区间二次筛选

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MMMMMMMW/article/details/81221375

素数筛选主要是求1---n中有多少个素数。

主流的有两种,一种是埃式筛法,一种是线性筛法,其中线性筛法是埃式筛法的升级版,但正常情况下埃式筛法就够用了。

然后有种不常用的,就是筛选出区间内的素数,通常这个区间内的值都很大,大到用线性筛也会超时,但区间的范围会相对比较小,这时候就要用区间二次筛选

埃式筛法

总所周知,一个合数总是由几个素数相乘得到的,换句话说,一个素数的倍数肯定就是合数了。

这样,我们只需要现将0---n先全部标记为素数,其中0和1先特判为非素数。然后从2开始,每次遇到一个素数,先记录下来,然后就通过循环来将它的倍数都标记为非素数即可,下次遇到时就可以直接跳过,而没被标记过的就肯定是素数了,因为一个素数的倍数不可能是素数。

下面是相关代码:

const int MAX = 1e7+7; 
int prime[MAX];
bool is_prime[MAX];
int cnt;
void Ai_Sieve(int n)
{
	cnt = 0;
	memset(is_prime,true,sizeof(is_prime));
	is_prime[0] = is_prime[1] = false;
	for(int i = 2;i <= n;i++)
	{
		if(is_prime[i])
			prime[cnt++] = i;
		for(int j = i+i;j <= n;j+=i)
			is_prime[j] = false;
	}
}

线性筛法:

埃式筛法虽然效率已经很高了,但还是有一些重复的地方,从而使时间复杂度稍微升高了那么一点点,但身为一个强迫症,肯定忍不了!

简单说一下为什么会有重复的地方

一个合数,是由几个素数相乘所得到的,换句话说,几个素数的倍数也都能组成合数

几个素数!!!

如果一个合数的素数都相同,倒也无妨,但如果不相同呢?每个不同的素数都需要标记一次相同的合数

比如6,由2和3组成,当遇到2时,它被标记一次;当遇到3时,它又被标记一次,强迫症忍得了?

那么,如何解决呢?

一个合数a,可以取出最小的素数因子x,其他剩下的因子y,那么,a = x *y。

我们只需要保证这个合数a只被最小的素数因子x标记一次即可!

那么,怎么做呢?

我们同样遍历一边2--n,定义为i

我们有一个prime数组存放之前遇到的素数,将prime[j] *i标记一次,然后j++

重点来了

如果遇到i % prime[j] == 0时,我们就放弃循环,为什么?

因为此时i已经有了prime[j]这个因子了,而prime[j+1]肯定比prime[j]要大,如果不终止,下次就相当于i * prime[j]被大于最小素数因子的素数筛选了!

举个例子

如果i = 6,我们先筛选2*6 = 12,然后由于6 % 2 = 0,所以终止,否则,下次就是筛选3 *6了,也就是让素数3来筛选,但是,此时3*6有比3还小的素数因子,也就是2 *9的2!不符合开始说的一个合数只被它最小的素数筛选!

下面是代码:

const int MAX = 1e7+7; 
int prime[MAX];
bool is_prime[MAX];
int cnt;
void Line_Sieve(int n)
{
	cnt = 0;
	memset(is_prime,true,sizeof(is_prime));
	is_prime[0] = is_prime[1] = false;
	for(int i = 2;i <= n;i++)
	{
		if(is_prime[i])
			prime[cnt++] = i;
		//从这里为止都跟埃氏筛一模一样
		for(int j = 0;j < cnt && i*prime[j] <= n;j++)//挑选之前选中的素数,与i相乘,结果标记为合数
		{
			is_prime[i*prime[j]] = false;
			if(i % prime[j] == 0)//如果i中有prime[j]的因子,那么,下一次循环的时候,i*prime[j+1]就会被标记,但是,此时的结果中有比prime[j+1]更小的素数因子
				break;
		}
	}
}

区间二次筛选

有时候,我们需要求区间[L,R]内的素数,但是,L,R都很大,但R-L却相对比较小,这时候就要用到区间二次筛选了。

首先,从 L 到 R ,如果在[2,√R]中没有素数可以整除它,那该数肯定是素数了

所以,我们先筛选出[2,√R]中的素数

此后,用埃式筛选的方法进行第二次筛选,选出[L,R]中的素数

其中最关键的就是直接跳到L了

在埃式筛中,我们通过prime[i] * j  = a来筛除掉a

但是,如果我们从1--R来遍历j,肯定就要超时了

所以,需要把j直接跳到所需筛除的区间内,因此j = L/prime[i]

如果有余数,j就需要加1,因为此时的j * prime[i]小于L,在区间范围外

如果j等于1,j就需要加1,否则机会吧prime[i]给直接筛去了

附上POJ 2689的AC代码:

#include <cstdio>  
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <list>
#include <map>
#include <stack>
#include <queue>
#include <set>
using namespace std;
#define ll long long
const int MAX = 1100000; 
int prime[MAX];
bool is_prime[1100000];
int cnt;
void Ai_Sieve(int n)
{
    cnt = 0;
    memset(is_prime,true,sizeof(is_prime));
    is_prime[0] = is_prime[1] = false;
    for(int i = 2;i <= n;i++)
    {
        if(is_prime[i])
            prime[cnt++] = i;
        for(int j = i+i;j <= n;j+=i)
            is_prime[j] = false;
    }
}
int Sieve2(ll a,ll b)
{
    int cnt2 = 0;
    Ai_Sieve(48000);
    memset(is_prime,true,sizeof(is_prime));
    if(a <= 1)
        a = 2;
    for(int i = 0;i < cnt && (ll)prime[i]*prime[i] <= b;i++)
    {
        int l = a/prime[i]+ (a%prime[i] != 0);//有余数时,说明求到的值小于需要的值,所以加1
        if(l == 1)//l等于1时,j初始化就是个素数,会排除掉这个素数
            l++;
        for(ll j = (ll)l*prime[i];j <=b;j+=prime[i])
            is_prime[j - a] = false;
    }
    for(int i = a;i <= b;i++)
        if(is_prime[i-a])
            prime[cnt2++] = i;
    return cnt2;
}
int main()
{
    ll a,b;
    while(cin >> a >> b)
    {
        int num = Sieve2(a,b);
        int max1,max2,min1,min2;
        max1 = 0,max2 = 1,min1 = 2e9,min2 = 0;
        for(int i = 1;i < num;i++)
        {
            if(prime[i] - prime[i-1] < min1-min2)
            {
                min1 = prime[i];
                min2 = prime[i-1];
            }
            if(prime[i] - prime[i-1] > max1-max2)
            {
                max1 = prime[i];
                max2 = prime[i-1];
            }
        }
        if(max1 == 0)
            printf("There are no adjacent primes.\n");
        else
            printf("%d,%d are closest, %d,%d are most distant.\n",min2,min1,max2,max1);
    }
    //cout << "AC" <<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/MMMMMMMW/article/details/81221375
今日推荐