筛素数
题目
给定一个正整数 n n n,请你求出 1 1 1~ n n n中质数的个数。
输入格式
共一行,包含整数 n n n。
输出格式
共一行,包含一个整数,表示 1 1 1~ n n n中质数的个数。
数据范围
1 < = n < = 1 0 6 1 <= n <= 10^6 1<=n<=106
思路
朴素筛法
- 做法
将 2 2 2~ n n n中所有的数的倍数都标记上,最后没有被标记的就是质数。
- 原理
如果一个数是合数,那么 n n n一定是由 2 2 2~ n − 1 n-1 n−1中的质数组成的,如果遍历了这些数都没有被标记,证明该数不是合数。时间复杂度约为 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))。
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n, cnt;
bool st[N];
void get_primes(int n)
{
for(int i = 2; i <= n; i++)
{
if(!st[i])
{
cnt++;
}
for(int j = i; j <= n; j = j + i)
{
st[j] = true;
}
}
}
int main()
{
cin >> n;
get_primes(n);
cout << cnt << endl;
return 0;
}
埃氏筛法
- 做法
对之前的朴素筛法进行一些优化,仅仅使用质数去筛。
- 原理
省去了使用合数进行不必要的重复筛选,可以被合数筛到的数,也一定可以被构成这个合数的质数筛到。时间复杂度约为 O ( n l o g ( l o g ( n ) ) ) O(nlog(log(n))) O(nlog(log(n)))。
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n, cnt;
bool st[N];
void get_primes(int n)
{
for(int i = 2; i <= n; i++)
{
if(!st[i])
{
cnt++;
for(int j = i; j <= n; j = j + i)
{
st[j] = true;
}
}
}
}
int main()
{
cin >> n;
get_primes(n);
cout << cnt << endl;
return 0;
}
线性筛法
- 做法
使用一个合数的最小质因子进行筛选,确保每个合数都可以被筛到并且仅仅筛选一次。
- 原理
1
1
1~
n
n
n之内的任何一个合数一定会被筛掉,而且筛的时候只用最小质因子来筛,
然后每一个数都只有一个最小质因子,因此每个数都只会被筛一次,因此线性筛法是线性的.
时间复杂度约为
O
(
n
)
O(n)
O(n)。
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n, cnt;
int primes[N];
bool st[N];
void get_primes(int n)
{
for(int i = 2; i <= n; i++)
{
if(!st[i])
{
//将遍历到的素数存入数组中
primes[cnt++] = i;
}
//进行筛选
//之所以使用primes[j] <= n / i 而不是 j < cnt && primes[j] <= n / i
//当 i 为合数,一定会遍历到 i 的最小质因数,因此一定会break
//当 i 为质数,primes[j] 中一定会遍历到本身,因此也一定会break
for(int j = 0;primes[j] <= n / i; j++)
{
//之所以将st[primes[j]*i] = true 放在 if 判断之前可以分两种情况讨论
//当 i % primes[j] == 0 , 也就代表 primes[j] 是 i 的质因数
//又因为 j 是从小到大遍历的 , 因此是最小质因数
//因此 primes[j] 也一定是 primes[j]*i的最小质因数
//当 i % primes[j] != 0 , 也就代表 primes[j] 一定小于 i 的最小质因数
//由于 primes[j] 一定是本身的质因数
// 因此 primes[j] 也一定是 primes[j]*i的最小质因数
st[primes[j]*i] = true;
//之所以将 i % primes[j] == 0 作为循环的break条件
//是为了保证每个合数都仅仅被它的最小质因数筛选一次
//当 i % primes[j] == 0 成立 , 也就代表 i 是一个合数,并且之前被primes[j]筛过
//因此接下来无论使用 i 和谁相乘得到的数都一定会是primes[j]的倍数
//也就代表不需要使用 i 来进行筛选了,i 选到的数也一定会被比它小的primes[j]选到
//如果重复选的话也就违背了一个数仅被它的最小质因数筛选一次的原则
if(i % primes[j] == 0)
{
break;
}
}
}
}
int main()
{
cin >> n;
get_primes(n);
cout << cnt << endl;
return 0;
}