★实验任务
在素数的大家庭中,有一种素数很特别,假设这个素数是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年不打已经成渣了,,,)
大概每周都会保证一定量更新吧…写题解是为了督促自己以及存储学习资料
尽量会保证题解注释通俗易懂(因为写的不通俗我自己也会看不懂…)
也许部分讲解存在错误(因为菜),希望带哥指正
有问题一起交流学习吧,算法还是很有趣的