三大素筛原理

素筛

注:1 不是素数

埃氏筛

这种方式无疑是最慢的,我们看一下如何加快,换一种思路:一个质数的倍数一定是合数,所以,假设P是质数,我们可以筛掉区间[1, 1e7]中所有P的倍数。
先看个例子,对于数列1~11:
在这里插入图片描述
先筛去2的倍数:
在这里插入图片描述
然后筛去3的倍数:
在这里插入图片描述
然后筛去5的倍数:
在这里插入图片描述
至此,1~11内的所有合数都被筛完了, 2 3 5 7 11是数列中的质数。
为什么这样能筛去所有的合数呢,因为一个合数一定能被分解为几个质数的幂的乘积,并且这个数的质因子一定是小于它本身的,所以当我们从小到大将每个质数的倍数都筛去的话,当遍历到一个合数时,它一定已经被它的质因子给筛去了

bool is_prime[MAX+5];//标记是否为素数
int prime[MAX/2];//存放素数
int cnt = 0;//已存放的素数个数
void isprime2() {
    memset(is_prime, true, sizeof is_prime);
    for(int i = 2; i <= MAX; i++) {
    if(is_prime[i]) {
    prime[cnt++] = i;
    for(int j = i + i; j <= MAX; j += i) //二次筛选法:i是素数,则i的倍数必不为素数,筛掉
    	is_prime[j] = false;
    }
}

欧拉筛

o(n)复杂度

埃氏筛是筛去每个质数的倍数,但难免,会有合数会被其不同的质因子多次重复筛去。这就造成了时间浪费。

比如说:120
120会被2筛去一次,3筛去—次,5筛去—次。多做了两次不必要的操作。
那么我们如何确保120只被2筛掉呢?
在埃氏筛中我们用了一个循环来筛除一个质数的所有倍数,即对于p来说,筛除数列:2*p, 3*p,…, k*p另外,我们是从小到大枚举区间中的每个数的,数列是:2, 3, 4,… , n

2 * p,3 * p, …,k * p

2,3,…n

会发现,第二个数列是第一个数列的系数。
所以,我们不需要用一个for循环去筛除一个质数的所有倍数,我们将所有质数存储到primes[]中,然后枚举到第i个数时,就筛去所有的primes[j] * i。这样就在每一次遍历中,正好筛除了所有已知素数的 i 倍。
如果 i%primes[j] ==0 ,我们就结束内层循环,为什么呢?

因为:

1 : i % primes[j] = = 0
2 : primes[j] ∗ k = i


3: primes[j + 1] ∗ i = X

将2式带入3中得: primes[j + 1] ∗ primes[j] ∗ k = X

因为: primes[j + 1] > primes[j],所以: primes[j + 1] ∗ k > i

primes[j + 1] ∗ k = i ′

则:4: primes[j] ∗ i ′ = X

所以如果用3式筛去 X 的话,当 i 等于 i′时,X又会被4式筛去一次,为了确保合数只被最小质因子筛掉,最小质因子要乘以最大的倍数,即要乘以最大的 i, 所以不能提前筛。

比如说 1 ,2,3,4,5,6,7,8,9,10,11, 12,
当 i = = 4 时 primes = {2, 3}
此时 i % 2 = = 0 , 如果不结束内层循环的话, 12会被 3 ∗ 4 筛掉, 当 i = = 6 ,12又会被 2*6 筛掉。

欧拉筛的核心思想就是确保每个合数只被最小质因数筛掉。或者说是被合数的最大因子筛掉。

上代码

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <set>
#include <map>
using namespace std;
const int N = 100000100;
bool on[N]; //判断下标是否为素数 
int prime[N];//存放素数的数组 
int n, cnt;

int main() {
	cin >> n;
	memset(on, 1, sizeof(on)); 
	on[0] = on[1] = 0;  
	for(int i = 2; i <= n; i++){
		if(on[i]) prime[++cnt] = i;
		for(int j = 1; j <= cnt && prime[j] * i <= n; j++){
			on[prime[j] * i] = false;
			if(i % prime[j] == 0) break;
		}
	}
	int tot = 0;
	for(int i = 0; i <= n; i++){
		if(on[i]) tot++;
	}
	cout << tot;
	return 0;
}

例题:https://www.luogu.com.cn/problem/P3912

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值