题目
P3912 素数个数
题目描述
求 1 , 2 , ⋯ , N 1,2,\cdots,N 1,2,⋯,N 中素数的个数。
输入格式
一行一个整数 N N N。
输出格式
一行一个整数,表示素数的个数。
输入输出样例 #1
输入 #1
10
输出 #1
4
说明/提示
对于 40 % 40\% 40% 的数据, 1 ≤ N ≤ 1 0 6 1 \le N \le 10^6 1≤N≤106。
对于 80 % 80\% 80% 的数据, 1 ≤ N ≤ 1 0 7 1 \le N \le 10^7 1≤N≤107。
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 8 1 \le N \le 10^8 1≤N≤108。
思路(注意事项)
使用 int a[N];
最终不能通过所有测试案例,主要有以下几个方面的原因:
1. 内存占用问题
- 数组类型导致的内存占用:
int
类型通常在大多数系统中占用 4 个字节的内存空间。当你定义int a[N];
且N = 1e8 + 1
时,数组a
所占用的内存大小为4 * (1e8 + 1)
字节,大约为 400MB。 - 内存限制:在一些在线评测系统(OJ)中,通常会对程序的内存使用量进行限制,一般限制在几百兆字节以内。使用
int
类型的数组会占用大量的内存,很容易超出内存限制,导致程序运行时出错,无法通过所有测试案例。
2. 空间浪费问题
- 布尔标记本质:在该算法中,数组
a
的作用是标记一个数是否为合数,只需要两种状态,即“是合数”或“是质数”,本质上用一个布尔值(true
或false
)就可以表示。 int
类型的浪费:int
类型有 32 位,可以表示 2 32 2^{32} 232 个不同的值,但在这个场景中,只需要 2 种状态,使用int
类型会造成大量的空间浪费。
解决方案
使用 bool
类型的数组可以有效解决上述问题:
- 内存占用小:
bool
类型通常只占用 1 个字节的内存空间,定义bool a[N];
时,数组a
所占用的内存大小为(1e8 + 1)
字节,大约为 100MB,相比int
类型的数组,内存占用减少了四分之三。 - 满足需求:
bool
类型正好可以表示“是合数”(true
)和“是质数”(false
)这两种状态,满足算法的需求。
纯代码
#include<iostream>
using namespace std;
const int N = 1e8 + 1;
bool a[N]; //很关键
int main(){
int n;
cin >> n;
for (int i = 2; i * i <= n; i++)
if(a[i] == 0) //很关键
for (int j = i * i; j <= n; j += i)
a[j] = 1;
int sum = 0;
for (int i = 2; i <= n; i ++)
if (a[i] == 0) sum ++;
cout << sum;
return 0;
}
题解(加注释)
#include<iostream>
using namespace std;
const int N = 1e8 + 1; // 定义数组的最大大小
bool a[N]; // 用于标记是否为合数的数组,a[i] = 0 表示 i 是质数,a[i] = 1 表示 i 是合数
int main(){
int n;
cin >> n; // 输入上限 n
// 埃拉托斯特尼筛法:标记所有合数
for (int i = 2; i * i <= n; i++) // 遍历从 2 到 sqrt(n) 的所有数
if(a[i] == 0) // 如果 i 是质数
for (int j = i * i; j <= n; j += i) // 标记 i 的所有倍数为合数
a[j] = 1;
// 统计质数的数量
int sum = 0;
for (int i = 2; i <= n; i ++) // 遍历从 2 到 n 的所有数
if (a[i] == 0) sum ++; // 如果 i 是质数,计数器加 1
// 输出质数的数量
cout << sum;
return 0;
}