1. 判断是否质数
试除法,注意边界条件,不能写成 i * i <= x,i大时会越界,也不要写i < sqrt(x),
这样每次循环都会调用sqrt,比较慢。
bool is_prime(int x)
{
if (x < 2) return false;
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
return false;
return true;
}
2. 试除法分解质因数
试除法。 注意边界条件(与上面一样)。
关键是思想,从小到大用质数去除。注意这里为什么不用判断i是不是质数,因为如果x % i == 0,假设这里的i不是质数,那么i一定能表示成 i = a * b的形式,这里的a或b一定有一个小于i的,也就是说,a或b一定是x的因子。但是由于我们是从小到大遍历i,并且除去的。所以遍历到i时,x一定没有小于i的质数,与前面矛盾。所以当x % i == 0时,i一定是质数。
注意末尾(注:这是因为如果x有一个大于 x / 2的因子,那么一定只有一个)判断下 x > 1(这里写成>1是为了方便,事实上,为了容易理解,我们可以在一开始保存n = x, 经过for循环之后判断 x >= n / 2 就可以。直接写x > 1是为了少写一个变量。
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
{
int s = 0;
while (x % i == 0) x /= i, s ++ ;
cout << i << ' ' << s << endl;
}
if (x > 1) cout << x << ' ' << 1 << endl;
cout << endl;
}
3. 朴素筛求质数
刚刚我们的算法1是判断一个质数是不是质数,那如果我们想判断一个区间中有多少质数怎么办呢,显然我们可以直接使用n次算法1。但是我们有更好的做法。
用我们刚刚算法二中的思想,我们从小到大遍历i,每次遍历到一个质数,就把范围内所有是这个质数倍数的数都记录为false。当我们遍历到false时直接跳过,遍历到true时再去标记。
这样做的正确性也很容易证明。因为我们任何一个合数都可以分解为质因数乘积的形式,那么我们遍历到一个质数的时候,会把对应的倍数给标记为false。由于我们是从小到大遍历,当遍历到一个数时,如果它没被标记,说明它一定不能被小于它的质数分解,那么它也一定是质数。
可以与算法二简单记忆一下,因为这两个算法都是遍历到一个数为true时,这个数一定是质数。
代码为:
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (st[i]) continue;
primes[cnt ++ ] = i;
for (int j = i + i; j <= n; j += i)
st[j] = true;
}
}
特别注意一点,第二个for循环写成j+=i的形式,不要真的把倍数写成乘法的形式比如 k = j * m(m从2开始递增),加法是比乘法快一点的。
4. 线性筛求质数
容易发现算法三筛的还有改进的空间,因为同一个数,可能会被标记多次,比如6会被2标记,也会被3标记,怎么改进呢?我们可以使用线性筛来解决这个问题。
根据唯一分解定理可知,每个合数都有一个最小素因子。而欧拉筛的基本思想是,让每个合数被其自身的最小素因子筛选,而不会被重复筛选。欧拉筛的框架和埃氏筛大致相同,区别点在于第二层循环对倍增过程的操作。
朴素筛是,只要是素数就进行倍增。而欧拉筛是用当前遍历到的数字i,去乘以已经在素数表中的素数。
我们先放代码,再对代码进行讲解。
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break;
}
}
}
因为 i 是从小到大进行循环,会乘以前面的每一个素数,这就保证了每个素数的倍数都不会被错过。也就是每个合数都会被筛掉,这就保证了算法的正确性。
注意奇怪的是有一个语句,我们为什么要加上这一句话呢?
if (i % primes[j] == 0) break;
为什么会有这句话呢?
当前数能被质数数组中的数整除,当前数一定是包含这个质数因子的合数。此时我们直接跳出,就意味着我们认为后面的合数一定能够之后被遍历到。
此时我们有i * primes[j+1],i * primes[j+2]..... 没有遍历到,因为i % primes[j] == 0, 所以i可以表示成i = a * primes[j],那么i * primes[j+1] 可以表示成a * primes[j] * primes[j+1],那么当i遍历到a* primes[j+1]时,一定能用primes[j]去筛掉i * primes[j+1],同理可得当i遍历到a*primes[j+2]时,一定能用primes[j]去筛掉i * primes[j+2]。而且此时的primes[j]一定是最小的质数。
阅读完毕,请去快速训练一些题目吧~
介绍完毕质数~下次我会分享约数的相关算法。