【质数】埃氏筛法、线性筛法(欧拉筛法)

埃氏筛法

核心思想:对于任意一个大于1的整数,它的 k (k > 1) 倍是一个合数。

怎么做呢? 用质数的 k 倍筛合数。
具体流程:我们可以从2开始遍历到n,遍历每一个数字 i 时,如果 i 没有被标记(i 是质数),那么就从小到大枚举它的 k (k > 1) 倍的数字(最大也不要超过 n),打上合数的标记。

小优化1:就是在枚举质数 i 的 k 倍数字时,可以从 i * i 开始枚举,因为小于 i * i 的数字都已经被枚举过了。

举个例子🌰:
请添加图片描述
当 i 从 2 枚举到 8 的时候,现在我们要开始筛掉所有 2~n 范围内 8 的倍数,将它们标记为合数。那么现在问题来了:要从 1 * 8、2 * 8、3 * 8 开始一直枚举吗?并不需要。如图所示,我们只需要从 8 * 8 开始枚举,因为小于 8 * 8 的倍数(7 * 8 、6 * 8、5 * 8、4 * 8 …)都已经在 i 到达 8 之前枚举过了。

模板代码:

void get_prime()
{
	for(LL i=2;i<=n;i++) //开LL防溢出 
	{
		if(!st[i]) //是质数 
		{
			for(LL j=i*i;j<=n;j+=i) //开LL 小优化:从i*i开始遍历 
			{
				st[j] = true;
			}
		}
	}
}

特点:根据代码我们可以看出来,埃氏筛法使用已经确定的质数去筛掉合数,遇到合数会直接跳过。

小优化2:任何一个合数 a 的两个因数必定一个大于 sqrt(a),另一个小于 sqrt(a) 。
于是我们在枚举 i 的时候,只用枚举到 sqrt(n) 即可。

代码如下:

void get_prime1()
{
	for(LL i=2;i<=n/i;i++) //只枚举到sqrt(n),将所有的合数打上标记 i<=n/i是防溢出写法
	{
		if(!st[i]) //是质数 
		{
			for(LL j=i*i;j<=n;j+=i)
			{
				st[j] = true;
			}
		}
	}

时间复杂度

O(nloglogn)
loglogn已经近似常数了,算是非常优的算法,但是埃氏筛法在标记合数的时候可能会重复多次访问同一个数字,如果我们能让每一个数字只被访问一次,那么时间复杂度就会降低到O(n),于是我们在线性筛里要解决这个问题。

线性筛法(欧拉筛法)

核心思想:要让一个合数被它的最小质因数筛掉。

怎么实现这个想法呢?
首先我们需要一个 p 数组将当前找到的质数都记录下来,因为我们要让一个合数被它的最小质因数筛掉。
然后我们从小到大遍历 2~n 的所有数字 i ,如果遇到没被标记的数字就加入质数数组 p ,然后通过 j 遍历质数数组,将 i 从小到大依次乘上当前找到的所有质数,然后筛掉这个乘积。当 j 遍历到某个质数,i 可以整除这个质数的时候就停止当前的 i 的枚举,进入到下一个 i 的枚举。

与埃氏筛法不同的氏,埃氏筛法所有筛掉的合数都是通过质数筛掉的;而线性筛法晒掉的合数不一定都是由质数筛掉的,合数也会参与筛合数。

总结:
从前往后遍历每一个 i

  • 用 i 的质数倍去筛
  • 遇到 i 是质数的倍数的时候就要停止

😕为什么遇到某一个质数可以整除 i 的时候就要停止呢?
简单理解就是当你遇到某一个质数,你是它的倍数的时候,这个时候筛掉的合数仍然是可以看作被这个最小质因数筛掉的。但是如果继续筛,你身上就携带了这个可以整除你的质数。

就拿图中的例子来说,遇到 9 * 3 的时候,由于 9 是 3 的倍数,于是我可以将 9 分解成 3 * 3 ,于是 9 * 3 就变成了 3 * 3 * 3(27),这个 27 就是被它的最小质因数筛掉的,但是如果继续筛 9 * 4 ,那么 36 就是被 4 这个质数筛掉的,但是换个视角看,9 * 4 可以分解为 3 * 3 * 4 ,最小的质数是 3,所以 36 实际上在将来是应该要被它的最小质因数 3 筛掉的,即 12 * 3 的时候筛掉这个 36。这样以来就导致了重复的访问。
请添加图片描述模板代码

void get_prime()
{
	for(LL i=2;i<=n;i++) //LL是防止 i * p[j]的时候溢出 
	{
		if(!st[i]) p[++cnt] = i; //没有标记就是质数,加入质数的数组 
		
		for(int j=1;i * p[j] <= n;j++) //j是p数组的下标,依次筛去i的质数倍 
		{
			st[i * p[j]] = true; //合数打上标记,筛掉 
			if(i % p[j] == 0) break; //如果i是该质数的倍数就停止 
		}
	}
}

特性:
if(i % p[j] == 0) break;这个判定条件能让每一个合数被自己的最小质因数筛掉。

  1. 如果 i 是合数,枚举到最小质因数的时候跳出循环
  2. 如果 i 是质数,枚举到自身时跳出循环
    注意,在筛的过程中,我们还能知道 p[j] 是 i 的最小质因数

模板题

P3383 【模板】线性筛素数
在这里插入图片描述

ac代码

#include<iostream>

using namespace std;

typedef long long LL; 

const int N = 1e8 + 10;

int n,q;
bool st[N];
int p[N],cnt;

//埃氏筛
void get_prime1()
{
	for(LL i=2;i<=n;i++)
	{
		if(!st[i]) //是质数 
		{
			p[++cnt] = i;
			for(LL j=i*i;j<=n;j+=i)
			{
				st[j] = true;
			}
		}
	}
}

//只枚举到sqrt(n)的埃氏筛 因为题目要求第k小的质数,所以还要单独遍历一遍st数组,似优化又好像非优化,都差不多
void get_prime1()
{
	for(LL i=2;i<=n/i;i++) //先枚举到sqrt(n),将所有的合数打上标记 
	{
		if(!st[i]) //是质数 
		{
			for(LL j=i*i;j<=n;j+=i)
			{
				st[j] = true;
			}
		}
	}

    for(int i=2;i<=n;i++) //再从2开始遍历整个2~n范围,将没有标记的数字加入质数数组 
        if(!st[i]) p[++cnt] = i;
}

//线性筛 
void get_prime2()
{
	for(LL i=2;i<=n;i++) //LL是防止 i * p[j]的时候溢出 
	{
		if(!st[i]) p[++cnt] = i; //没有标记就是质数,加入质数的数组 
		
		for(int j=1;i * p[j] <= n;j++) //j是p数组的下标,依次筛去i的质数倍 
		{
			st[i * p[j]] = true; //合数打上标记,筛掉 
			if(i % p[j] == 0) break; //如果i是该质数的倍数就停止 
		}
	}
}

int main()
{
	scanf("%d%d",&n,&q);
	get_prime1();
	
	while(q--)
	{
		int k; cin >> k;
		printf("%d\n",p[k]);
	}
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值