<数论>素数筛法
埃氏筛法
介绍
如果我们要得到自然数n以内的全部素数,把不大于根号n的所有素数的倍数剔除,剩下的就是素数。
算法分析
因此如果 x是质数,那么大于x的x的倍数\(2x,3x,4x……kx\)(k是整数)一定不是质数,只要将范围内的数字剔除剩下的就是素数了。是不是很简单_
代码
对于1~n的数字,我们先建立一个数组isprime并且全部赋值为1
bool isprime[N];
memset(isprime, 1, sizeof(isprime));
这里也能用vector动态的开辟(推荐)
vector<bool>isprime(N, 1);//大小为N 默认为1
然后对根号n以内的素数i 然后对其倍数 将其筛掉,标记数组为0
for (int i = 2; i * i < N; i++)
{
if (isprime[i])//如果i是素数 那么就开始剔除它的倍数
{
for (int j = i + i; j < N; j=j+i)
{
isprime[j] = 0;
}
}
}
对上述筛法进行说明
根据分解定理:合数一定能被分解成几个质数的乘积
当从小到大遍历到数 i 时,倘若它是合数,则它一定是某个小于 \(i\)的质数 \(x\) 的整数 \(k\),既\(i=x*k\),故根据此方法的步骤,当最开始遍历到x的时候,开始筛选x的倍数,会将\(isprime[kx]\)的标记为0,所以我们一会把\((i=kx)\)这个数字筛掉,故从小到大,合数都一定能被之前的素数通过加倍从而筛掉
进阶优化
结论:当我们找到素数x的时候,其实从x*x为起点(而不是i*2)进行筛选即可_。
因为对于 小于x 的整数倍\(k, 2x,3x,4x……kx。\)
1、当k为质数的时候,\(kx\)一定会被小于x的质数k,通过加倍筛掉,
2、当k为合数的时候,k可以分解成$ i*(k/i) * x$ [i为k的质因子,并且k/i为整数],所以 当我们之前对质数i进行加倍的时候,一定会把kx筛掉。
所以我们对素数x进行筛的时候,对于小于\(x^2\)的整数 \(k,2x,3x,4x……x^2\)都已经被筛过了,因此从\(x^2\)开始筛即可。
优化后的代码
for (int i = 2; i * i < N; i++)
{
if (isprime[i])//如果i是素数 那么就开始剔除它的倍数
{
for (int j =i*i; j < N; j=j+i)
{
isprime[j] = 0;
}
}
}
线性筛法
介绍
埃氏筛实际上存在重复的部分,比如数字30,会被质数3和质数5重复筛掉,就会浪费时间,因此我们需要优化算法从而使每个数字只被筛一次,使得时间复杂度降成 线性的时间复杂度。所以就出现了线性筛。时间复杂度O(n)
算法思路k
数字\(x\),其最小质因数\(k\),将小于等于\(k\)的所有质数\(p_i\)乘上数字\(x\),并将\({p_i}*{x}\)的最小质因数标记为\(pi\),质数的最小质因数是本身。
分析
-
为什么\({p_i}*{x}\)的最小质因数是\(p_i\)?
\(p_i<=k\),\(p_i\)是比\(x\)的最小质因数还小或者相等的质数,乘起来肯定是\(p_i\)是最小质因数了。因为\(p_i<=k\),所以每个数字只能被筛一次。
-
如何保证每一个合数一定会被筛掉?
每一个合数都能分解成质数的乘积,质数\(p_i\)乘上一个合数实际上也是质数在乘质数,并且这个\(p_i\)还是最小的一个质因子,\({p_i}*{x}\),\(p_i\)和\(x\)的乘积可以构成新的合数,因为\(x\)是递增的,并且\(p_i\)是唯一的,因此\({p_i}*{x}\)只会被筛一次。
代码
int p[N];//最小值质因数
int prime[N];//用来存素数集合
int cnt=0;//目前素数集的个数
for (int i = 2; i <= N; i++) {
if (p[i] == 0) {//如果这个数字没被之前数字筛到,那么就是质数
p[i] = i;
prime[cnt++] = i;//把这个放到质数集里面
}
for (int j = 0; j < cnt; j++) {
if (prime[j] > p[i] || prime[j] * i > N) {//超出范围了 不需要
break;
}
p[prime[j] * i] = prime[j];
}
}