欧拉筛法及其改进方法(素数的判断)

483 阅读2分钟

素数的筛法(欧拉筛法)及其改进

欧拉筛法的算法分析:

1.使用一个prime[N]存下当前素数
2.使用一个on[N]数组标志i 是不是素数。
3.每一个素数都将其当前的i倍数标记为非素数(凡是有因子的数一定不是素数)。

处理函数

void init(){
	on[0] = on[1] = 1;
		//0和1不是素数
	for(int i = 2; i <= n; ++i){
		if(!on[i])	prime[++cnt] = i;
				素数存入栈中
		for(int j = 1; j <= cnt && i * prime[j] <= n; ++j){
			on[i * prime[j]] = 1;
				标记为不是素数
			if(i % prime[j] == 0) break;
	此处是精髓,如果当前的i能够被整除,那么这个数之前一定已经被处理过了,
		(欧拉筛法的关键)
		}		
	}
	return ;
}

欧拉筛法的关键

i % prime[j] == 0 这一步到底有什么用?
答:保证每个数只被它的最小质因子筛一次。(比较难以理解)
我们假设 i % prime[j] = 0成立,那么设k = i / prime[j] 则prime[j]*k = i成立
那么对于下一个需要筛选的 i * prime[j+1]=prime[j]kprime[j+1]
我们令m = k *prime[j+1]> i
因此当i增大时,k * prime[j+1]肯定会被筛出来的,因此我们这一次就不用处理了
进而我们就保证了每一个非素数只被它的最小质因子筛选一次。

例题

【题目描述】

求1,2,⋯,N 中素数的个数。

【输入输出格式】

【输入】:
一个整数N。
【输出】:
1 个整数,表示素数的个数。
注意:
• 对于40% 的数据,1≤ N ≤10 6
• 对于80% 的数据,1≤ N ≤10 7
• 对于100% 的数据 1≤ N ≤10 8

【算法分析】

这么大的数据如果真的要让普通判断方法,那么一定会超时(O(n2)).那么就需要一个黑科技来解决——欧拉筛法

【代码实现】

#include<bits/stdc++.h>
using namespace std;
const int N = 100000100;

int cnt, n;
int prime[N];
bool on[N];
int main()
{
	scanf("%d", &n);
	on[0] = on[1] = 1;
		//0和1不是素数
	for(int i = 2; i <= n; ++i){
		if(!on[i])	prime[++cnt] = i;
			//素数存入栈中
		for(int j = 1; j <= cnt && i * prime[j] <= n; ++j){
			on[i * prime[j]] = 1;
				//标记为不是素数
			if(i % prime[j] == 0) break;
				//此处是精髓,如果当前的i能够被整除,那么这个数之前一定已经被处理过了,然后直接跳过。(欧拉筛法的关键)
		}		
	}
	long long tot = 0;
	for(int i = 2; i <= n; ++i)
  	if(!on[i]) tot++;
	printf("%lld\n", tot);  //计算数量
							//不过作者此处还有一个思路,就是由于存进去时候是有序的,所以使用一下low_bound()[上一篇博客有使用方法],或者二分查找也可以的。
	return 0;
}

总结

上述代码中on数组和prime数组均为准确的

(on[] == 1 表示不是素数 on[] ==0表示是素数)

------------------------------------------------------------------------------------------------

2021年后续(欧拉筛法的优化 O(n / 2))

此处只给出处理的函数代码

#include<bits/stdc++.h>
using namespace std;

int ct, prime[100010];
int on[100010];
void pre(int n){
	memset(on, 1, sizeof(on));
	on[0] = on[1] = on[4] = 0;
	on[2] = 1; prime[++ ct] = 2;
	for(int i = 3; i <= n; i += 2){
		if(on[i])	prime[++ ct] = i;
		for(int j = 1; j <=ct && prime[j] * i <= n; ++ j){
			on[prime[j] * i] = 0;
			if(i % prime[j] == 0) break;
		}
	}
	return ;
}
int main(){
	int n;
	scanf("%d", &n);
	pre(n);
	printf("%d", ct);
	return 0;
}

要注意优化后的代码中on数组对于偶数的标记不准确,但是prime数组依旧准确。

(所有的素数中只有2一个偶数)