对应洛谷题目:【模板】线性筛素数
参考文章线性筛的原理
给自己看的,不过多分析
代码
#include<iostream>
using namespace std;
//线性筛的原理就是用一个 素数 * 正整数(有点像二维数组遍历) 的遍历完所有的范围的同时避免重复计算同一个结果
//其中避免重复计算的原理是让每一个合数都只被他的最小质因子筛一遍
//且素数这一维是在遍历的过程中更新的
const int N = 1e8+10;
int n,q;
bool mark[N]; //标记合数,默认没标记
//为什么mark默认全都不是合数呢(就是全false
//因为1不是素数直接跳过,2、3都是素数刚好从2开始筛
//而根据线性筛的原理,比2、3大的合数可由小的素数筛出(如4 = 2*2、6 =2*3……),大的素数如果没有被筛出,那必是素数,mark自然就“正确了”
int primes[N];
int cnt;
int main()
{
ios::sync_with_stdio(0);
cin >> n >> q;
int half =n/2;
for (int i = 2; i <= half; i++)
//因为肯定至少会有一次*2,所以i到一半,2*i的部分都筛完了,也侧面证明了mark的“正确性”
{
if(!mark[i]) primes[++cnt] = i;
for (int j = 1; primes[j] <= n/i; j++) //用除法,防止越界
{
mark[primes[j] * i] = true;//这里就是在筛选出合数
if(i % primes[j] == 0) break;
//break 这一行的判断成立时,说明i里面包含此primes[j]这个质因数了
//number = primes[j+1] * i(也就是不break的话,下一个要标记的合数)会被{primes[j] * (i + k)}(break掉之后随着i继续++会出现的 i+k)给标记掉!
//因为primes[j]才是number的最小质因子!
//为什么 break 这一行要在mark标记完之后再检测?
//目的是作为最小质因子的primes[j]每遇到一个新的数要先乘一次,再break,确保primes作为最小质因子的所有倍数都被筛一遍
}
}
for (int i = half; i <= n; i++)//剩下一半早就筛完了
{
if(!mark[i]) primes[++cnt] = i;
}
for (int i = 1; i <= q; i++)
{
int index;
cin >> index;
cout << primes[index] <<endl;
}
}
内存优化
使用bitset减少mark数组的内存占用,好像也能减少运行时间
灵感来源:【 C++算法】20分钟学会高效地素数筛法,埃氏筛法,欧拉筛法
#include <iostream>
#include <bitset>
using namespace std;
const int N = 1e8+10;
int n,q;
bitset<N> marks; //这里
int primes[N];
int cnt;
int main()
{
ios::sync_with_stdio(0);
cin >> n >> q;
int half =n/2;
for (int i = 2; i <= half; i++)
{
if(!marks[i]) primes[++cnt] = i;
for (int j = 1; primes[j] <= n/i; j++)
{
marks[primes[j] * i] = 1;
if(i % primes[j] == 0) break;
}
}
for (int i = half; i <= n; i++)
{
if(!marks[i]) primes[++cnt] = i;
}
for (int i = 1; i <= q; i++)
{
int index;
cin >> index;
cout << primes[index] <<endl;
}
}
下面是其他筛法
简单判断
#include <iostream>
#include <cstring>
#include <math.h>
using namespace std;
bool checkPrime(int x)
{
double sq = sqrt(x);
//优化、用x的根号减少作判断限高判断次数
//如果i^2 > x 则如果x是合数,肯定存在一个大于i的和一个小于i的相乘等于x,那i小于根号x时就应该判断出来是合数了
for (int i = 2; i <= sq; i++)
{
if(x % i == 0) return false;
}
return true;
}
int main()
{
double start = clock();
int n = 1e6;
long long cnt = 0;
for (int i = 2; i <= n; i++)
{
if(checkPrime(i))cnt++;
}
cout << cnt << endl;
}
朴素筛法
原理是素数的倍数一定是合数
#include <iostream>
#include <cstring>
#include <math.h>
using namespace std;
const int N = 1e6;
int main()
{
int cnt = 0;
bool prime[N+10];
memset(prime,true,N+10);
prime[1] = false;
int sq = sqrt(N);
for (int i = 2; i <= sq; i++)
{
if(prime[i])
{ //下面把2*i改成 i*i 就变成了埃式筛法(注意避免int超界死循环)埃式筛法避免了 i和比自己小的素数相乘,减少了重复标记
//但是还是会有重复!e.g.12会被2筛掉,但会被3*3=9之后+3再筛一次
for (int j = 2 * i; j <= N; j+= i)//从2i开始,因为i本身是素数,把它所有倍数都标记为合数,但这样会有重复标记!
prime[j] = false;
}
}
for (int i = 1; i <= N; i++)
{
if(prime[i])
cnt++;
}
cout << cnt <<endl;
}