素数筛有多种实现方法,以下是常见的几种素数筛选算法:
1. 埃拉托斯特尼筛法(埃氏筛法):从小到大遍历每个数,如果当前数为素数,则将其所有倍数标记为合数。这种方法适用于小范围的素数筛选。
2. 线性筛法:结合了埃氏筛法和欧拉筛法的优点,通过线性方式遍历筛选,只标记每个合数一次,同时能够记录素数。
3. 欧拉筛法:对于每个素数,遍历其倍数并标记为合数,同时确保每个合数只被标记一次。这种方法相对于埃氏筛法能够更高效地进行筛选。
4. 素数表法:通过提前生成素数表,然后直接查询表中的素数来进行筛选。这种方法适用于需要多次查询素数的情况,但对于较大范围的素数筛选,生成素数表的开销较大。(这里不举例了)
这些算法都有各自的优点和适用场景,选择适当的素数筛选算法取决于具体的问题需求和素数范围。
埃拉托斯特尼筛法(埃氏筛法)
埃拉托斯特尼筛法(埃氏筛法)是一种简单直观的素数筛选算法,其基本思想是从小到大遍历每个数,如果当前数为素数,则将其所有倍数标记为合数。
算法步骤如下:
1. 创建一个布尔类型的数组 `isPrime`,用于标记每个数是否为素数。初始时,将数组中的所有元素都设置为 `true`。
2. 从 `2` 开始遍历到目标范围内的最大值,记为 `N`。
3. 对于当前遍历到的数 `i`,如果 `isPrime[i]` 为 `true`,则将 `i` 的所有倍数(除了 `i` 本身)标记为合数。具体操作是从 `2*i` 开始,以 `i` 为步长,将数组中对应位置的元素设置为 `false`。
4. 遍历完成后,数组中为 `true` 的索引即为素数。
以下是使用埃氏筛法进行素数筛选的示例代码:
vector<int> sieveOfEratosthenes(int n) {
vector<int> primes;
vector<bool> isPrime(n + 1, true);
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
primes.push_back(i);
for (int j = 2 * i; j <= n; j += i) {
isPrime[j] = false;
}
}
}
return primes;
}
线性筛
线性筛法是一种高效的素数筛选算法,它结合了埃拉托斯特尼筛法和欧拉筛法的优点。相比于埃氏筛法,线性筛法只标记每个合数一次,同时能够记录素数。
算法步骤如下:
1. 创建一个整型数组 `primes`,用于存储筛选得到的素数。
2. 创建一个布尔类型的数组 `isPrime`,用于标记每个数是否为素数。初始时,将数组中的所有元素都设置为 `true`。
3. 从 `2` 开始遍历到目标范围内的最大值,记为 `N`。
4. 对于当前遍历到的数 `i`,如果 `isPrime[i]` 为 `true`,则将其添加到素数数组 `primes` 中。
5. 遍历素数数组 `primes`,并将 `i` 与当前素数相乘得到的数标记为合数,同时判断是否小于等于 `N`。
6. 遍历完成后,素数数组 `primes` 中的元素即为筛选得到的素数。
以下是使用线性筛法进行素数筛选的示例代码:
vector<int> linearSieve(int n) {
vector<int> primes;
vector<int> isPrime(n + 1, 0);
for (int i = 2; i <= n; i++) {
if (isPrime[i] == 0) {
primes.push_back(i);
isPrime[i] = i;
}
for (int j = 0; j < primes.size() && primes[j] <= isPrime[i] && i * primes[j] <= n; j++) {
isPrime[i * primes[j]] = primes[j];
}
}
return primes;
}
欧拉筛法
欧拉筛法(Euler's Sieve)是一种高效的素数筛选算法,通过每个合数只被它的最小质因数筛去一次,从而达到线性时间复杂度的效果。
算法步骤如下:
1. 创建一个布尔类型的数组 `isPrime`,用于标记每个数是否为素数。初始时,将数组中的所有元素都设置为 `true`。
2. 创建一个整型数组 `primes`,用于存储筛选得到的素数。
3. 从 `2` 开始遍历到目标范围内的最大值,记为 `N`。
4. 对于当前遍历到的数 `i`,如果 `isPrime[i]` 为 `true`,则将其添加到素数数组 `primes` 中。
5. 遍历素数数组 `primes`,并将 `i` 与当前素数相乘得到的数标记为合数,同时判断是否小于等于 `N`。标记的方法是将合数对应位置的 `isPrime` 值设置为 `false`。
6. 遍历完成后,素数数组 `primes` 中的元素即为筛选得到的素数。
以下是使用欧拉筛法进行素数筛选的示例代码:
vector<int> eulerSieve(int n) {
vector<int> primes;
vector<bool> isPrime(n + 1, true);
for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
primes.push_back(i);
}
for (int j = 0; j < primes.size() && i * primes[j] <= n; j++) {
isPrime[i * primes[j]] = false;
if (i % primes[j] == 0) {
break;
}
}
}
return primes;
}
素数筛拓展应用
素数距离
Problem:2380
Time Limit:1000ms
Memory Limit:65535K
Description
给定2个整数L,R(1<=L,R<=2^31,R-L<=10^6),求闭区间【L,R]中相邻两个质数差值最小的数对与差值最大的数对。当存在多个时,输出靠前的素数对
Input
输入2个数L和R, L小于R,R-L<=10^6,本题多组输入
Output
如果没有相邻的2个素数,请输出"There are no adjacent primes." 否则输出:最小的数对与差值最大的数对。 注意看例子。
Sample Input
10 100 20 100000 10 20 10 12
Sample Output
11,13 are closest, 89,97 are most distant. 29,31 are closest, 31397,31469 are most distant. 11,13 are closest, 13,17 are most distant. There are no adjacent primes.
这道题不能直接使用上述任何筛法,因为题目所给的数据范围是int,而上述的素数筛一般使用范围在1到1e7
所以我们结合题目寻找一些素数的性质帮助我们解这道题:
任何素数的因子只有1和它本身,所以合数除了1和它本身外一定还有其他因子,易知其中一个因子<=sqrt(本身),所以即使是做大的合数,他亦必有一个因子<=sqrt(int)<1e5。
一个合数一定可以被素数分解(素因子分解)
这样我们利用上述性质便可将题目数据缩小:我们只需找出所有1到1e5内的所有素数,再将L到R内所有的1到1e5内的所有素数的倍数筛掉,剩下的便是素数了。
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<map>
#include<set>
#include<stack>
#include<queue>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5,M=1e7+5;
int arr[N],L,R;
vector<int>prime,p;
int v[M];
void init() {
arr[1] = 1;
for (int i = 2; i < N; i++) {
if (arr[i] == 0)
prime.push_back(i);
for (int j = 0; j < prime.size()&&prime[j]*i<N; j++) {
arr[prime[j] * i] = 1;
}
}
}
int main() {
init();
while (scanf("%d%d", &L, &R) != EOF) {
p.clear();
memset(v, 0, sizeof(v));
for (int i = 0; i < prime.size(); i++) {
LL pp = prime[i];
for (LL j = max((L + pp - 1) / pp * pp, pp*2); j <= R; j += pp) {
v[j - L] = 1;
}
}
for (int i = 0; i <= R-L; i++) {
if (v[i] == 0&& i + L > 1) {//注意这个1的特判,我被它卡了好久
p.push_back(i+L);
}
}
if (p.size() < 2) {
printf("There are no adjacent primes.\n");
}
else {
int c, mn = 1e8, mx = 0, f;
for (int i = 1; i < p.size(); i++) {
if (p[i] - p[i - 1] > mx) {
f = i;
mx = p[i] - p[i - 1];
}
if (p[i] - p[i - 1] < mn) {
c = i;
mn = p[i] - p[i-1];
}
}
printf("%d,%d are closest, %d,%d are most distant.\n", p[c - 1], p[c], p[f - 1], p[f]);
}
}
return 0;
}
最后介绍一个埃氏筛的改进代码,很快,比上面三种都要快
const int N = 1e8 + 5;
bool arr[N];
vector<int>p;
void init() {
for (int i = 2; i * i <= N; i++) {
if (arr[i] == 0)
for (int j = i * i; j <= N; j += i) {
arr[j] = 1;
}
}
for (int i = 2; i <= N; i++) {
if (arr[i] == 0)
p.push_back(i);
}
}