1.1 好素数

本文介绍了一种特殊素数——好素数的概念,并通过欧拉筛法高效地寻找指定范围内的好素数。欧拉筛法是一种优秀的素数筛选工具,通过对素数倍数的巧妙处理实现了O(n)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

★实验任务

在素数的大家庭中,有一种素数很特别,假设这个素数是x,若在区间[x-10,x)以及(x,x+10]中都存在素数,我们就把x叫做好素数。
现在CYP学长遇到了一个问题,给定一个N,请问N以内的好素数一共有多少个呢? 注意:素数指的是除了1和它本身以外不再有其他因数的自然数。

★输入格式
只有一行,一个正整数N。(N<=100000)。
对于30%的数据,n<=100
对于80%的数据,n<=5000
对于100%的数据,n<=100000

★输出格式
只有一行,一个整数表示好素数的个数。

★输入样例
10

★输出样例
3

本题的关键在于求素数(废话),为了不爆时间复杂度,比较好的方法肯定是埃氏筛法( O(nloglogn) )或者欧拉筛法( O(n) )了
这里使用欧拉筛法,因为我没写过埃氏筛法

欧拉筛法是相当优秀的求素数工具,建议积累掌握

//Matsuri
#include<cmath>
#include<cstring>
#include<cstdio>
#include<iostream>
#define MAX 100001

int N,n=0;
int prime[MAX];  // prime确实是用来存找到的素数的,但切记它找出的素数不全,只是为了服务于筛法这个过程(把这数组理解成存储各种筛子的工具箱) 
bool number[MAX]; // 数字的标记,1为素数,0为合数(由于memset函数的一些问题,用欧拉筛时请把该数组定义为bool类型)

int isgoodprime(int x)
{
	int l; // 好素数的区间1左上限x-10可以处理一下,减少冗余操作 
	int tmpcnt=0; // 记录有多少个区间里找到了素数  
	if (x-10<2) l=2;
	else l=x-10;
	
	for (int t=l;t<x;t++)
	{
		if (number[t])  
		{
			tmpcnt++;
			break; // 区间1内找到素数了
		}  
	}
	for (int t=x+1;t<=x+10;t++)
	{
		if (number[t])
		{
			tmpcnt++;
			break; // 区间2内找到素数了
		}  
	}
	if (tmpcnt==2) return 1;
	else return 0;
}

int main()
{
	int cnt=0; 
	
	scanf("%d",&N);
	
	// 欧拉筛找素数 
	memset(number,1,sizeof(number)); //初始化,所有数字都被标记为素数 
	for (int i=2;i<=N;i++)  // 0和1剔除 
	{
		if (number[i]) prime[cnt++]=i; // 若i是素数,那么就把它存进prime数组里,作为筛这一工具 
		for (int j=0;j<cnt&&i*prime[j]<=N;j++)  //注意循环判断条件(不过界) 
		{
			number[i*prime[j]]=0; // 无论i是合数还是素数,它的倍数一定不是素数,故全筛掉 
			if (i%prime[j]==0) break; // 欧拉筛精华:使时间复杂度为O(n)的关键 
			
			/* 
			   引用另外某个博客的分析(忘记哪里看到的):
			   由于 i 的每次循环内,j 都是从0开始的,所以每个合数都至少会被它的最小素因子筛掉一次
			   举例:12 一定会在 i=6,prime[0]=2 时被筛掉一次,6一定会在 i=3,prime[0]=2 时被筛掉一次
			   而最好的算法当然是 使每个合数都只被它的最小素因子筛掉一次,
			   即 12 在i=6时被 prime[0]=2 筛过一次就足够了,它不需要在 i=4 时被 prime[1]=3 再筛一次
			   虽然时间上 i=6 , prime[0]=2 会出现在 i=4 , prime[1]=3 后面,
			   但这不重要,我们要阻止前者的多余操作,这就是上面那句关键代码干的事情
			   实在不理解就背下来
			*/ 
			  
		} 
	}
	
	for (int i=2;i<=N;i++)
	{
		if (number[i]) n+=isgoodprime(i);
	}
	
	printf("%d",n);
	return 0;
}

欧拉筛在注释里,由于才疏学浅或许讲解得并不清楚,很不好意思
我们来聚焦一些细节,我在代码的一开始就有写到:

bool number[MAX]; // 数字的标记,1为素数,0为合数(由于memset函数的一些问题,用欧拉筛时请把该数组定义为bool类型)

首先来简单了解一下memset()函数,它能够对新申请的内存进行初始化,在C++中,我们可以用数组来体现这一过程

//使用memset()需要头文件#include<cstring>
memset(number,1,sizeof(number)); //初始化,所有数字都被标记为素数

第一个参数放入待处理数组,第二个参数为统一初始化值,第三个参数为将要初始化的数据规模
sizeof()函数在这里返回这个数组的总字节数(感兴趣的可以再对原理作深入了解,对今后写代码有帮助)
那么之前说的memset()函数的问题是什么?
memset()函数对int类型的数组进行处理时,一般将统一初始化值取0或者-1
举例:若初始化值为1,由于1的二进制值为 00000001,而int类型字节数为4,则对数组的一个值,会创建4个00000001,每个占字节数为1,初始化时会用这4个00000001组合成一个int值即00000001000000010000000100000001,该值转成十进制时为(16843009),显然不是我们想要的,可以自己验证一下

引用他人解释:
仅可在初始化的值的最后8位为11111111(255)或00000000(0)时能够正确进行初始化。也就是说int型数组仅能初始化为-1和0,其余方法均不能得到正确的结果。

对char类型和bool类型的数组进行初始化时,由于char,bool类型的数据均为1字节,不会出现上述问题,故能正常按我们的想法来初始化数组

memset()函数也是很有用的工具,但目前我还并没有完全了解它的原理…但代码里的用法可以积累一下

比memset()函数更好用的是fill()函数

//使用fill()需要头文件#include<algorithm>
fill(first,end,val): 

参数格式为首地址,末地址,欲初始化量,处理范围是前闭后开区间
举例:fill(a+1,a+100,0),a为数组(任意类型),这样a数组从下标为1到99的数据都被初始化为0
从原理上比较memset()与fill():
memset()原理为将某一块内存中的指定单元内容全部设置为ch指定的ASCII值,即0 、1
fill()原理为将某一块内存中的指定单元直接赋成指定的值,任何值都可以

====================================================================

第一次写题解,请多包涵大约3年前的noip之后再也没打过像样代码的菜鸡…
借某个契机重新开始学习数据结构和算法,已经完完全全退化成什么都不会了(以前就是个半路出家的菜鸡,3年不打已经成渣了,,,)
大概每周都会保证一定量更新吧…写题解是为了督促自己以及存储学习资料
尽量会保证题解注释通俗易懂(因为写的不通俗我自己也会看不懂…)
也许部分讲解存在错误(因为菜),希望带哥指正
有问题一起交流学习吧,算法还是很有趣的

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值