数论 · 质数

素数又称质数,是基础数论中的重要内容。

该文章主要分为三个部分:

  • 质数定理
  • 算法模板及时间复杂度分析
  • 例题

1. 质数定理

1.1 质数除\(1\)和他本身之外没有其他因数

1.2 \(2\)是唯一一个偶数质数, 同时也是最小的质数, \(1\)不是质数

1.3 除了 \(1\) 之外的任何一个数不是质数就是合数(很明显, 却很常用)

1.4 能整除一个合数的最小因子一定小于等于\(\sqrt{n}\), 换言之, 如果一个数不能被\(2 - \sqrt{n}\)之间的任何一个数整除,那么这个数是质数

2. 代码模板及时间复杂度分析

2.1 试除法判定素数

上面已经说过, 如果一个数不能被\(2 - \sqrt{n}\)之间的任何一个数整除,那么这个数是质数

所以只要遍历 \(2 - \sqrt{n}\) 之间的所有数,判断能否整除 \(n\) 即可
时间复杂度最坏可以达到 \(O(\sqrt{n})\)

bool is_prime(int n)
{
	if (n <= 1) return false; // 1并不是质数 
	for (int i = 2; i <= n / i; i ++ ) // 遍历从2 - sqrt(n) 之间的所有 
		if (n % i == 0) // 判断是否能整除n 
			return false;
			
	return true;
}

2.2 筛法

筛法的作用就是筛出从 \(1 - n\) 之间的所有质数
筛法主要有两种

2.2.1 埃氏筛法

思路:遍历所有从 \(2 - n\) 之间所有数, 若数\(i\)是质数, 则将\(i - n\)之间所有能被\(i\)整除的标记为合数

Q: 为什么只标记质数的倍数就可以了呢??
A: 根据唯一分解定理: 每个\(>= 2\)的数的质因数分解形式只有一种, 且一定可以被质因数分解, 所以我们只需要考虑质数的倍数,就可以标记所有的合数

复杂度分析:
首先要遍历 \(2 - n\), 复杂度为 \(O(n)\)
其次要标记所有合数, 复杂度为 \(O(\frac{n}{p1} + \frac{n}{p2} ... )\)
而$\frac{n}{1} + \frac{n}{p2} ... $ 可以写成 \(n(\frac{1}{p1} + \frac{1}{p2} ... )\)

$\frac{1}{1} + \frac{1}{2} ... $ 被称为调和级数,大小约为 \(logn\)
而我们只对其中的素数进行向后的筛选,所以总复杂度为\(O(loglogn)\)

总时间复杂度为 \(O(nloglogn)\), 约为线性的复杂度

void get_primes(int n)
{
	for (int i = 2; i <= n; i ++ )
	{
		if (!st[i]) primes[ ++ cnt] = i;
		else continue;
		
		for (int j = i + i; j <= n; j += i)
			st[j] = true;
	}
}
2.2.2 欧拉筛法

这是数学中使用频率最高的筛法, 思路同埃氏筛法, 不同的我们标记合数的时候, 是用前面已经得到的素数去更新后面的

void get_primes(int n)
{
	for (int i = 2; i <= n; i ++ )
	{
		if (!st[i]) primes[ ++ cnt] = i; // 如果这个数没有被标记为合数,则为质数
		
		for (int j = 1; primes[j] * i <= n; j ++ ) // 枚举前面已经得到的质数去更新后面的,同时要保证 primes[j] * i <= n, 因为更新大于n的数就没有意义了
		{
			st[i * primes[j]] = true;
			if (i % primes[j] == 0) break; // 小优化
			/* 
			1. 当 i % primes[j] != 0时, 说明此时遍历到的 primes[j] 不是 i 的质因子,那么只可能是此时的 primes[j] < i 的
                        最小质因子,所以primes[j] * i的最小质因子就是primes[j];
                        2. 当有 i % primes[j] == 0 时,说明i的最小质因子是 primes[j] ,因此 primes[j] * i的最小质因子也就应该是
                        prime[j],之后接着用 st[primes[j + 1] * i] = true去筛合数时,就不是用最小质因子去更新了,因为 i 有最小
                        质因子 primes[j] < primes[j + 1] ,此时的 primes[j + 1] 不是primes[j + 1] * i 的最小质因子,此时就应该
                        退出循环,避免之后重复进行筛选。
                        */
		}
	}
}

时间复杂度为 \(O(n)\), 是线性复杂度

欧拉筛的代码稍有复杂,但是速度更快(但实际上个人在实际问题中,更喜欢写埃氏筛,因为比较好些啦 ~ )

3. 例题

  1. Luogu P3383 线性筛模板

这道题非常讨厌,只能写欧拉筛才能过去(www写不了埃氏筛了)

#include <iostream>
#include <cstring>
#include <cstdio>
#include <ctime>

using namespace std;

const int N = 1e8 + 10;

bool st[N];
int primes[N], cnt;
int n, q, x;

void get_primes(int n)
{
	for (int i = 2; i <= n; i ++ )
	{
		if (!st[i]) primes[ ++ cnt] = i;
		
		for (int j = 1; primes[j] * i <= n; j ++ )
		{
			st[i * primes[j]] = true;
			if (i % primes[j] == 0) break;
		}
	}
}

int main()
{
	scanf("%d%d", &n, &q);
	get_primes(n);
	
	while (q -- )
	{
		int x;
		scanf("%d", &x);
		printf("%d\n", primes[x]);
	}
	
	return 0;
}
posted @ 2022-03-29 16:12  Link-Cut-Y  阅读(313)  评论(0编辑  收藏  举报