C++数学-数论筛质数-埃氏筛和线性筛流食般投喂

不仅要看,看完算法流程还需要自己手动复述呦~


筛质数的引入:

素数的判定那一块讲的是如何判断一个数是素数,如果此时想知道 1,n 中有多少个素数,或者是 1,n 中的素数里面,第 k 个素数是多少?

一个自然的想法就是从 2 开始,依次向后对每一个数进行一次质数检验。但是这种解法相对暴力,本篇就介绍两种方法,能够快速地将 1,n 中的素数全部记录下来。


埃氏筛:

算法思想:

📌任意大于 1 的质数,它的 k(k > 1) 倍就是合数。所以我们就从小到大考虑每个数,把当前数的所有倍数记为合数,没有标记的数就是素数。

💡**代码段描述:**从 2 开始遍历到 n,遍历到当前 i 没有被标记的一定是素数,先记录、统计,再开始筛。

📌**小优化:**找到一个质数 x 的时候,我们可以从该数的 x 倍向后筛,因为小于 x 的倍数一定被之前的筛过了,例如:5 * 2,变个式,2 * 5,其实就是在找到质数 2 的时候已经筛过了。
时间复杂度:

埃氏筛的时间复杂度为:O(n loglog n)。不好证,知道就行。


线性筛:

线性筛法又称欧拉筛法。

算法思想:

📌在埃氏筛的基础上做优化,上述的埃氏筛会重复标记同一个合数,线性筛就是在埃氏筛的基础上,同一个合数只被标记一次,时间复杂度将会优化到 O(n)。

💡如何做到同一个合数只会被标记一次呢?

💡让每个合数被它的最小质因数筛掉!

🤡从前往后遍历每个数 i,用 i 的质数倍去筛,同时,判断 i 是不是质数的倍数,是就停止,确保合数是被它的最小质因数筛掉的。
时间复杂度:

线性筛的时间复杂度是 O(n)。

真实是 O(n + n),因为大循环 2~n 这是一个 n;筛的过程中,每个数要么被标记成合数,要么被加进 prime数组,这也是一个 n;所以每个数经历两次操作,遍历 + 筛。


OJ题来源:洛谷

OJ题名:【模板】线性筛素数

OJ题归属:数学-数论【筛质数】

解题算法:埃氏筛法 / 线性筛法

cpp 复制代码
#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_prime()
//{
//	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;
//			}
//		}
//	}
//}

// 线性筛
void get_prime()
{
	for (LL i = 2; i <= n; i++)
	{
		if (!st[i]) p[++cnt] = i;

		// 合数被它的最小质因数筛掉
		for (LL j = 1; i * p[j] <= n; j++) // j 是 p数组的下标
		{
			st[i * p[j]] = true; // 筛
			if (i % p[j] == 0) break; // 如果 i 是 质数的倍数,用这个筛的话,就满足不了合数被它的最小质因数筛掉了
		}
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> q;

	get_prime();

	while (q--)
	{
		int k; cin >> k;
		cout << p[k] << endl;
	}

	return 0;
}