在算法竞赛中很多时候要用到素数,因此快速有效地得到素数就是一个重要技能了。本蒟蒻在此列举几种求 n n n以内素数的方法。
1.暴力(傻瓜算法)
这是最没用的算法,时间复杂度太高:枚举 n n n以内的每一个数,再用试除法判断该数是否为素数。
code
//Maxn 为数组q的大小。
bool q[Maxn}
bool check(int n)
{
for(int i = 0 ; i * i < n ; i ++)
if(n % i == 0)
return false;
return true;
}//试除法
void make_q(int n)
{
for(int i = 2 ;i <= n ; i++)//1和0既不是质数也不是合数,应单独判断
if(check(i))
q[i] = true
}
2.Eratosthenes筛法
最常用的素数筛,其原理是每枚举到一个素数就将其倍数全标记为合数,那么没有统计到的就是素数。计算到 n \sqrt{n} n时就可以筛出小于 n n n的所有素数。
code
bool q[Size];//Size为n的最大值。
void make_q(int n)
{
q[1] = false;
q[0] = false;
for(int i = 2 ; i * i <= n ; i++)//初始化。
q = true;
for(int i = 2 ; i * i <= n ; i++)
{
for(int j = i * i ; j <= n ; j += j)
{
q[j] = false;
}
}
}
复杂度
O
(
n
×
log
n
)
O(n\times\log{n})
O(n×logn)
但在这种情况下还是会有大量点被重复标记,所以可以选择只筛素数的倍数,这与筛所有数的倍数是等效的(参照算术基本定理)。
code
bool q[Size];
void make_q(int n)
{
q[0] = false;
q[1] = false;
for(int i = 2 ; i * i <= n ; i++)
{
if(q[i])
{
for(int j = i * i ; j < n ; j++)
{
q[j] = true;
}
}
}
}
复杂度 O ( n × log log n ) O(n\times\log{\log{n}}) O(n×loglogn)
3.线性筛法(欧拉筛)
埃氏筛虽然复杂度接近线性但还是有一定偏差,所以欧拉筛应运而生欧拉筛是一种线性复杂度的素数筛算法。原理基础是算术基本定理。基于算术基本定理,每一个数一定可以表示为若干个质数之积(质数除外)。所以每一个数的最小质因子是确定的,因此我们可以用最小质因子来标记每一个数,对于每一个数
i
i
i而言,对于每一个小于
i
i
i的最小质因子的质数
j
j
j都可以筛掉
i
×
j
i\times j
i×j这个数。且对于不同的
i
i
i而言满足
i
×
j
i\times j
i×j不重不漏。
举例:一开始枚举
2
2
2时将
2
2
2标记为质数并将其最小质因数定为
2
2
2,然后将
2
×
2
2\times2
2×2的最小质因数标记为
2
2
2,接下来枚举
3
3
3时先将小于
3
3
3的
2
2
2与之相乘得到的
6
6
6的最小质因子标记为
2
2
2然后再算
3
×
3
3\times3
3×3的最小质因子。像这样每次将小于等于该数的最小质因子的数与这个数本身相乘,就能做到不重不漏。
具体看代码
code
int f[Size], q[Size];
void make_q(int n)
{
int top = 0;
for(int i = 2 ; i <= n ; i++)
{
if(!f[i])
{
f[i] = i;
q[top++] = i;
}
for(int j = 0 ; p[j] <= f[i] && j < top ; j++)
{
f[i * p[j]] = p[j];
if(p[j] > n / i)//当所枚举到的质数与当前数的积大于n时退出。
break;
}
}
}
复杂度 O ( n ) O(n) O(n)。