【笔记】 素数筛法(朴素筛法,优化筛法,线性筛法)
素数在OI中的应用很广
很多题都会用到素数,那么如何得到素数就是件很重要的事了
暴力枚举并判断?当然可以但实在太暴力了一点
考虑到如果一个数i是素数,那么i的倍数显然不可能是素数
由此我们可以通过不断的筛去不是素数的数,从而得到剩下的素数
----------------------------------------------------------------------------------------------
算法1.朴素算法:
假设我们要得到1~10中的素数
那么首先我们已知1不是素数,最小的素数为2,先列出一个1~10的数组
1 2 3 4 5 6 7 8 9 10
直接将1划去得到:
1 2 3 4 5 6 7 8 9 10
从2开始筛去它的倍数:
1 2 3 4 5 6 7 8 9 10
然后我们向后接着找是素数的数,继续以3来筛去它的倍数:
1 2 3 4 5 6 7 8 9 10
枚举到4发现它不是素数,直接跳过,一直枚举到10为止,我们就得到了一个有标记的数组
1 2 3 4 5 6 7 8 9 10
最后剩下的数(即标红的数)就是我们所要的素数了
从这一个例子开始,我们可以轻松地推广至求1~n的素数
那么只需从2开始枚举至n并删去素数的倍数就可以了
计算量为n/2+n/3+n/4+...,是一个调和级数,时间复杂度为O(nlogn)
代码如下:
void is_prime (int n)
{
memset (vis, 0, sizeof (vis));
for (int i = 2; i <= n; ++i)
for (int j = i + i; j <= n; j += i)
vis[j] = 1; //vis[]用来判断是否为素数,打上标记的都为合数
}
---------------------------------------------------------------------------------------------------------------------------------
算法2.优化算法:
我们注意到朴素算法中比如36会被2,3,4,6,9,12,18这6个数筛到,相当于被2*18,3*12,4*9,6*6,9*4,12*3,18*2筛到,实际上
a*b和b*a是等价的,于是,我们可以通过枚举小的那个因子,来优化一半的计算量,复杂度不变为O(nlogn),常数上优化了
代码如下:
void is_prime (int n)
{
int up = (double) sqrt(n*1.0) + 1;
for (int i = 2; i <= up; ++i)
for (int j = i * i; j <= n; j += i)
vis[j] = 1;
}
---------------------------------------------------------------------------------------------------------------------------------
算法3.线性筛法:
虽然优化算法的复杂度已经不是那么大了,但我们发现,就算枚举小的因子,一个数x依旧会被1或多个数筛过,
即对一个数有重复的筛去,所以我们考虑怎么样能够让一个数只被筛去一次呢?
那么便是我们的线性筛法,我们考虑筛去一个数时,只由它的最小素因子来进行,就可以做到O(n)的筛素数
代码如下:
void is_prime (int n)
{
for (int i = 2; i <= n; ++i)
{
if (!vis[i])
p[++cnt] = i;
for (int j = 1; j <= cnt; ++j)
{
if (i * p[j] > n)
break;
vis[i * p[j]] = 1; //p[j]是i*p[j]的最小素因子
if (i % p[j] == 0) //再往后p[j]就不是最小素因子了
break;
}
}
}
上一篇: 输出打印1-500之间的质数