主要我想讲讲 “ 碍事(埃氏) ” 筛法
(一)介绍
埃氏筛法
这是一个我感觉和辗转相除法一样NB的算法。原理也很简单。
首先将2到n范围内的整数写下来。其中2是最小的素数。将表中所有的2的倍数划去,
表中剩下的最小的数字就是3,他不能被更小的数整除,所以3是素数。
再将表中所有的3的倍数划去…… 以此类推,如果表中剩余的最小的数是m,那么m就是素数。
然后将表中所有m的倍数划去,像这样反复操作,就能依次枚举n以内的素数,
埃氏筛法的时间复杂度是0(n*log(logn))。
(二)对比
如果原理你都理解了 那么你直接去后边看用法
如果 你还没有理解 那么 我再举个例子
最朴素的判断素数方法 是这样的 对吧
int sushu(int n)
{
for(int i = 2; i <= sqrt(n); i++)
{
if (i % n == 0)
{
return 0;//不是素数
}
return 1;//是素数
}
}
//需要sqrt (n) 次循环才能判断出来n是素数
那么 现在我让你判断1-20 之内的素数,你会怎么做从1 遍历到20 挨个判断一下是不是素数 ,对吧?
那么我们来分析一下复杂度,你就认为我们算一下循环次数好了。
从1-20 需要遍历20次,对于每个数,最倒霉情况下 需要sqrt(n)次才完成能判断。
这一共是多少次呢 sqrt(2) + sqrt(3) + sqrt(4) + sqrt(5) + ......... sqrt(20) 次。
通过计算器计算得到:答案应该60,也就是需要60次。
那么我们再来看下,我们使用埃氏筛法需要几次:
for(int i = 2; i <= maxn/* 最大到哪 */; i++) //从2开始往后筛.
{
if (sushu[i])
{
for(int j = i + i; j <= maxn; j += i)
{
sushu[j] = 0;
}
}
}
首先,外面的循环:毋庸置疑,从2-20遍历:需要20次(其实是19,我们忽略四舍五入为20)
我们来看里面的操作:从2开始,2是素数,那么4不是、6不是、8也不是、10、12、14、16、18、20 都不是,这是9次操作
然后3开始:6、9、12、15、18都不是素数:这是5次操作
然后4跳过,5跳过,6跳过
从7开始:14不是素数,这是1次
操作后边的8、9、10、11、12、14、15、16、18、20都会跳过,而11、13、17 、19都不会操作 因为他们的倍数大于20了。
那么一共进行了 20 + 9 + 5 + 1 = 35次操作,比那个60少了将近一半。
其实因为举的例子很小:只到20。所以你看到的才快了这么点。
你可以想一下:如果数字变得很大的时候,比如一千万,你用普通的筛法,是需要更多次数的。
所以,这个埃氏筛法快的其实不止这么一点~~(至于究竟快多少 先不管了 你只要知道很快就好了)
(三)下面说下 怎么用它
明白了原理用起来就很灵活了
比如:
例一 求1-20之间的素数个数
int main()
{
int ans = 0;//用来计数
bool sushu[21];
for(int i = 0; i <= 20; i++)
{
sushu[i] = 1; //先全部置为真
}
sushu[0] = sushu[1] = 0;//1 0 不是素数
for(int i = 2; i <= 20; i++) //从2开始往后筛
{
if(sushu[i])
{
for(int j = 2 * i; j <= 20; j += i)
{
sushu[j] = 0;
}
}
if(sushu[i])
{
ans++;//如果是素数 就计数
}
}
cout << ans;
return 0;
}
例二 就是我们的原题
#include<iostream>
const int N = 5e6 + 10;//总的范围规定在这里
using namespace std;
bool su[N];
void sushu()//我们将这个埃氏筛法写成一个函数
{
for(int i = 0; i <= N; i++)
{
su[i] = 1;//先全部置为真
}
su[0] = su[1] = 0;//1 0 不是素数
for(int i = 2; i <= N; i++) //从2开始往后筛
{
if(su[i])
{
for(int j = i + i; j <= N; j += i)
{
su[j] = 0;
}
}
}
}
int n, m;
int main()
{
/*
我们在程序刚开始先调用这个函数
把这个 sushu 数组处理成我们想要的样子:用来判断素数。
这可能就是预处理的思想。
我们在开头处理这一次把 sushu 数组里面下标是素数的全部变成了 "1"
后边想判断是不是素数直接用 sushu[i] 是不是真就好了
*/
sushu();
int ans = 0;//用来计数
cin >> n >> m;//输入头和尾
for(int i = n; i <= m; i++)//遍历头到尾,判断就行了
{
if(su[i])
{
ans++;//如果是素数 就计数
}
}
cout << ans;
return 0;
}
例三 输入一个数n 判断他是不是素数(多组测试数据)
n的范围是 (n<1e6)
这个题很有意思:题上说有多组测试数据(没说几组)。
每次给你一个n那么其实意思就是说:有很多个n让你判断他们是不是素数而且 n 最大是1e6。
那么你们想一下:如果我第一个数给你1000000,你用普通的判断素数的方法:需要sqrt(n)次才能判断出来,
也就是差不多3000次才能判断出来是不是素数,对吧?
3000次也不多。那如果我输入一共一万组呢?就需要3000 * 10000次了吧,所以:这个时候就体现了 “预处理” 的重要性。
我们先预处理出来 1e6 以内的所有素数,这样不管你输入啥我直接去看是不是素数就好了,对吧?
那么 “预处理” 之前我也想到了,但是用最普通的筛法去做预处理。
那么你想一下:1e6!每个你都需要去判断一下是不是素数。
我刚才在上边算了 20 以内的素数需要 60 次才能判断出来。那1e6以内需要多少次呢?
我告诉你:大概需要1e9次(也就是十亿次)
而用埃氏筛法需要几次呢?我直接告诉你们:需要70万次。
十亿 和 七十万 差别不用我说了吧?
这就是这个算法的效率 也是算法的魅力所在。
#include<stdio.h>
#include<algorithm>
#include<math.h>
const int maxn = 1e6 + 7;//总的范围规定在这里
using namespace std;
bool sum[maxn];
void sushu()//我们将这个埃氏筛法写成一个函数
{
for(int i = 0; i <= N; i++)
{
su[i] = 1;//先全部置为真
}
su[0] = su[1] = 0;//1 0 不是素数
for(int i = 2; i <= N; i++) //从2开始往后筛
{
if(su[i])
{
for(int j = i + i; j <= N; j += i)
{
su[j] = 0;
}
}
}
}
int n;
int main()
{
sushu();//预处理
//输入 n
while(scanf("%d",&n)!=EOF)
{
if(sum[n])
{
printf("Yes\n");
}
else
{
printf("No\n");
}
}
return 0;
}
(四)~~~~~
好像还有一个更厉害的叫欧拉筛法(线型筛法),我根本不知道那是啥啊