筛法是一种在数论和算法领域常用的算法策略,用于高效地找出一定范围内满足特定条件的数。最经典的筛法是埃拉托色尼筛法(Sieve of Eratosthenes),用于找出小于等于给定正整数 n 的所有质数。
筛法在密码学、数论计算、算法竞赛等领域有着广泛的应用,能够高效地解决与质数相关的各种问题。所以就让我来带领大家一起学习这把黄金钥匙吧。go~go~go~出发咯
1.欧拉筛
- 欧拉筛,也叫线性筛,核心在于保证每个合数仅被其最小质因数筛去,从而实现线性时间复杂度 O(n)。
- 对于任意一个合数 N,它都可以唯一分解为 N = p_1^{a_1}p_2^{a_2}\cdots p_k^{a_k},其中 p_1\lt p_2\lt\cdots\lt p_k 为质数。欧拉筛就是利用每个合数必有最小质因数这一特性,通过从小到大遍历质数表和所有数,确保只在最小质因数处筛除该合数。
C语言代码详细解析
#include <stdio.h>
#include <stdbool.h>
void eulerSieve(int n) {
bool isPrime[n + 1];
int prime[n];
int primeCount = 0;
// 初始化,假设所有数都是质数
for (int i = 0; i <= n; i++) {
isPrime[i] = true;
}
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
prime[primeCount++] = i;
}
for (int j = 0; j < primeCount && i * prime[j] <= n; j++) {
isPrime[i * prime[j]] = false;
if (i % prime[j] == 0) {
break;
}
}
}
// 输出所有找到的质数
for (int i = 0; i < primeCount; i++) {
printf("%d ", prime[i]);
}
}
int main() {
int n = 30;
printf("小于等于 %d 的质数有: ", n);
eulerSieve(n);
return 0;
}
- 初始化部分:
- bool isPrime[n + 1]; 创建一个布尔数组 isPrime ,用于标记每个数是否为质数。
- int prime[n]; 创建一个数组 prime 来存储找到的质数。
- int primeCount = 0; 初始化质数计数器为0。
- 通过循环将 isPrime 数组所有元素初始化为 true ,即假设所有数都是质数。
- 筛选部分:
- 外层循环 for (int i = 2; i <= n; i++) 从2开始遍历到 n。如果 isPrime[i] 为 true ,说明 i 是质数,将其存入 prime 数组,并增加质数计数器 primeCount 。
- 内层循环 for (int j = 0; j < primeCount && i * prime[j] <= n; j++) 遍历已找到的质数表。对于每个质数 prime[j] ,标记 i * prime[j] 为合数( isPrime[i * prime[j]] = false )。
- 当 i % prime[j] == 0 时, break 跳出内层循环。这是因为此时 i 是 prime[j] 的倍数,后续的 prime[j + 1] * i 等合数会在 i 增大到 i / prime[j] * prime[j + 1] 时,以 prime[j + 1] 为最小质因数被筛除,避免重复筛除。
- 输出部分:最后通过循环遍历 prime 数组,输出所有找到的质数。
优势与应用场景
- 优势:相比埃拉托色尼筛法,欧拉筛的时间复杂度为 O(n),效率更高,因为它避免了对合数的重复标记。在处理较大范围的数时,这种优势更加明显。
- 应用场景:
- 数论计算:在计算与质数相关的问题,如质因数分解、欧拉函数计算等方面有广泛应用。例如,计算一个数的欧拉函数 \varphi(n),需要先找出 n 的所有质因数,欧拉筛可以高效地提供所需的质数列表。
- 密码学:在一些密码算法中,质数的生成和筛选是重要的环节。欧拉筛的高效性有助于快速生成满足条件的大质数,保障密码系统的安全性。
- 算法竞赛:在竞赛题目中,涉及质数查找、素数相关性质计算等问题时,欧拉筛是常用的工具之一,可以帮助选手快速解决问题,提高解题效率。
学到这是不是感觉牛批的很呢,那我们看看模板题练练火热的手吧。
题目背景
本题已更新,从判断素数改为了查询第 k 小的素数。
提示:本题输入输出、运算数据量较大。
对于 C++ 语言,如果你使用 cin 来输入输出,建议使用 std::ios::sync_with_stdio(0) 来加速,同时使用 '\n' 换行输出。
对于 Java 语言,使用线性筛并且优化输入输出,也可以在规定时限内通过本题,但是时限可能较紧张。
对于 Python 语言,语言性能差异较大,需要使用到 numpy 库的数组以替代列表,且使用埃氏筛法,依然可以在合适的时间和内存消耗下通过本题。
题目描述
如题,给定一个范围 n,有 q 个询问,每次输出第 k 小的素数。
输入格式
第一行包含两个正整数 n,q,分别表示查询的范围和查询的个数。
接下来 q 行每行一个正整数 k,表示查询第 k 小的素数。
输出格式
输出 q 行,每行一个正整数表示答案。
输入输出样例
输入 #1复制
100 5
1
2
3
4
5
输出 #1复制
2
3
5
7
11
说明/提示
【数据范围】
对于 100% 的数据,n=10
8
,1≤q≤10
6
,保证查询的素数不大于 n。
Data by NaCly_Fish.
题解代码:
#include <cstdio>
#include <cstring>
bool isPrime[100000010];
//isPrime[i] == 1表示:i是素数
int Prime[6000010], cnt = 0;
//Prime存质数
void GetPrime(int n)//筛到n
{
memset(isPrime, 1, sizeof(isPrime));
//以“每个数都是素数”为初始状态,逐个删去
isPrime[1] = 0;//1不是素数
for(int i = 2; i <= n; i++)
{
if(isPrime[i])//没筛掉
Prime[++cnt] = i; //i成为下一个素数
for(int j = 1; j <= cnt && i*Prime[j] <= n/*不超上限*/; j++)
{
//从Prime[1],即最小质数2开始,逐个枚举已知的质数,并期望Prime[j]是(i*Prime[j])的最小质因数
//当然,i肯定比Prime[j]大,因为Prime[j]是在i之前得出的
isPrime[i*Prime[j]] = 0;
if(i % Prime[j] == 0)//i中也含有Prime[j]这个因子
break;
}
}
}
int main()
{
int n, q;
scanf("%d %d", &n, &q);
GetPrime(n);
while (q--)
{
int k;
scanf("%d", &k);
printf("%d\n", Prime[k]);
}
return 0;
}
看到这里看来你已经学的差不多了,下一期我将给你们介绍另一种也很厉害的筛法。