素数筛
埃氏筛
原理:每找到一个素数,便将以该素数为因子的所有合数标记,最终未被标记的数即为素数。
复杂度:据说是 \(O(n\log\log n)\) 。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e8+1.5, M = 6e6;
bool vis[N];
int prime[M];
void getPrime()
{
// int times = 0; // 记录进入内循环的次数
int &cnt = prime[0];
int i;
for (i = 2; i * i < N; ++i) {
if (vis[i]) continue;
prime[++cnt] = i;
for (int j = i * i; j < N; j += i) {
vis[j] = 1; // ++times;
}
}
for (; i < N; ++i) {
if (!vis[i]) prime[++cnt] = i;
}
// cout << cnt << endl; // 5761455
// cout << times << endl; // 242570204
}
int main()
{
getPrime();
cout << "1e9 内共 " << prime[0] << " 个素数" << endl;
return 0;
}
/*
运行时间:约1.2s
*/
欧拉筛
原理:每遍历到一个数,便将 \(i * prime[j]\) 标记,同时保证 \(prime[j]\) 是其最小质因子,从而保证每个合数只被标记一次。
复杂度:\(O(n)\)。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e8+1.5, M = 6e6;
bool vis[N];
int prime[M];
void getPrime()
{
// int times = 0; // 记录进入内循环的次数
int &cnt = prime[0];
for (int i = 2; i < N; ++i) {
if (!vis[i]) prime[++cnt] = i;
for (int j = 1; j <= cnt && i * prime[j] < N; ++j) {
vis[i * prime[j]] = 1; // ++times;
if (i % prime[j] == 0) break;
}
}
// cout << cnt << endl; // 5761455
// cout << times << endl; // 94238544
}
int main()
{
getPrime();
cout << "1e9 内共 " << prime[0] << " 个素数" << endl;
return 0;
}
/*
运行时间:约0.61s
5761455 + 94238544 = 99999999, 每个合数恰好被标记一次
*/

学以致用
有了素数筛,我们能干什么呢?
首先让我们来欣赏一下集智慧美貌于一身的诸葛大力吧。


“271是第58个质数”
“从7开始11个连续质数之和”
大力说得究竟对不对呢?就让掌握了素数筛的我们来实践检验一下吧。
改写主函数如下:
void think(char *wordsInHeart){}
int main()
{
getPrime();
/** 验证“271是第58个质数”*/
cout << "第58个素数是: " << prime[58] << endl;
if (271 == prime[58]) think("不愧是诸葛大力!");
else {
puts("疑?大力怎么会错呢?一定是有bug!");
exit(0);
}
/** 验证“271是从7开始11个连续质数之和”*/
int pos = 1;
while (prime[pos] != 7) ++pos; // 寻找素数7所在位置
int sum = 0;
for (int i = 0; i < 11; ++i) {
cout << (i?" + ":"") << prime[pos+i];
sum += prime[pos+i];
}
cout << " = " << sum << endl;
if (271 == sum) printf("哇!大力好厉害!");
else puts("疑?大力怎么会错呢?一定是有bug!");
return 0;
}
得到结果如下:


浙公网安备 33010602011771号