目录
很多时候我们解决问题的时候会需要产生某个区间的素数的模板,那这个时候如果区间很小,如何求解都无所谓;但如果这个区间很大,就涉及到要优化求解素数模板的算法,否则会额外降低效率。
一、朴素算法:
最直接的想法就是遍历这个区间内的所有数字并逐个利用试除法判断是否是素数加以统计。我们就用1~n这个区间为例:
int primes[MAXN]; //用来存储1~n之间的素数
int c = 0; //1~n之间素数的个数
for(int i = 2; i <= n; i++)
{
bool flag = true;
for(int j = 2; j * j < i; j++)
{
if(i % j == 0)
{
flag = false;
break;
}
}
if(flag)
{
primes[c++] = i;
}
}
这个算法的复杂度是,n的范围较大时显然不适用。求解一个素数模板甚至会超出解决问题所需要的时间。此时我们用到下面的两种方法。
二、优化的素数筛法
优化的角度与思路都是一样的,我们可以用已有的素数作为因子把它的倍数(一定是合数)筛掉,这也是优化的方法为什么叫做筛法的原因。
1、埃氏筛法(埃拉托斯特尼筛法)
每统计出一个质数p,就把他的倍数(1~x倍直到x * p < maxn)全部标记为合数,下次筛到这个数直接跳过,没被筛掉的就是下一个要统计的素数。代码如下:
int prime[MAXN + 1]; //存储1~n之间的所有素数
int visit[MAXN + 1]; //标记数组值为0的下标是素数,为1的是合数
int cnt; //素数个数
void solve(int n) //统计1~n之间的素数
{
cnt = 0;
memset(visit, 0, sizeof(visit)); //先全部置为0(素数),后续会筛掉
for(int i = 2; i <= n; i++)
{
if(!visit[i]) //统计到了一个素数
{
prime[cnt++] = i;
for(int j = 2 * i; j <= n; j++) //筛的过程,这里可以优化,下面讲解
{
visit[j] = 1;
}
}
}
}
实际上,上面有一处我标记了可以优化,那就是利用已找出的一个素数筛掉它的倍数的过程。我们呢每次都从2 * i开始筛,我举个例子大家一下子就看明白哪里是多余的了:我们
- 用2这个素数筛掉了2*2, 2*3, 2*4, 2*5, ... , 2*10(2*2*5=4*5), ..., 2*k1;
- 用3这个素数筛掉了3*2, 3*3, 3*4, 3*5, ..., 3*k2;
- 用5这个素数筛掉了5*2, 5*3, 5*4, 5*5, ..., 5*k2;
实际上,在以3和5为基准去筛的时候,很多都是之前被筛过了的,因此每次我们只需要从i * i起筛,而并不需要从2 * i起筛。
这个算法的复杂度大致是
2、欧拉筛法
虽然埃氏筛法已经能解决大部分问题,但是其仍做了许多重复筛选的工作,比如12被2和3同时筛过(即使我们优化过)。因此,对于更大范围的区间,我们需要使用复杂度更低的筛法——欧拉筛。
欧拉筛能使每个合数仅被筛一次,这样,我们可以在的复杂度内统计出1~n之间的所有素数。先贴代码,边看边讲解:
int prime[MAXN]; //存储素数的数组
int visit[MAXN]; //标记数组
int cnt; //统计出的素数个数
void solve(int n)
{
cnt = 0;
memset(visit,0, sizeof(visit));
for (int i = 2; i <= n; i++)
{
if (!visit[i])
{
prime[cnt++] = i; //纪录素数
}
for (int j = 0; j < cnt && i * prime[j] <= n; j++)
{
visit[i * prime[j]] = 1;
if (i % prime[j] == 0)
{
break;
}
}
}
}
欧拉筛有两处要讲解:
- 第二重循环的作用:埃氏筛法中,我们用一个素数的倍数去筛合数,在这里,我们用已筛出的素数作为因子去筛合数。
- 为什么要在i % prime[j] == 0时退出循环:如果i % prime[j] == 0,即当 i是prime[j]的倍数时,i = k * prime[j],如果继续筛prime[j + 1],那么i * prime[j + 1] = prime[j] * k * prime[j + 1],当i = k * prime[j+1]时会重复,所以才跳出循环。用一个例子帮大家理解一下:当i = 8 ,j = 1时,prime[j] = 2,如果不跳出循环,prime[j + 1] = 3,对应到上面的式子,8 * 3 = 2 * 4 * 3 = 2 * 12,在i = 12时仍然会筛一次。实际上这涉及到一些数学知识,为什么这样就能只筛一次,笔者也不求甚解了。