西北工业大学NOJ平台:质数与分解质因数专题(共4篇题解)
前言
质数和分解质因数是非常基本的数学知识,在各类算法中都有非常广泛的运用,本篇文章将大致介绍在这方面常用的一些算法,并以NOJ中的题目作为示例:
前置知识
质数
设一个正整数p
不为0
或1
。如果p
除了平凡约数(即其自身和1)外没有其他约数,那么称p
为质数(或称素数/不可约数)。
互质
互质是公约数只有1的两个整数,叫做互质整数。
最大公约数
指两个或多个整数共有约数中最大的一个,通常记作(a, b)
或gcd(a, b)
。
通常,我们运用辗转相除法来求两个数的最大公约数,以下是代码实现:
1 | int gcd(int a, int b) { |
正确性证明:若
b
不为0
,则a
可以被表达为a = kb + r
的形式,其中k
为整数,k = [a / b]
([]
表示高斯函数),r = a % b
(%
表取余操作)。当某个因数c
满足b % c == 0
且a % c == 0
,则其必然满足r % c == 0
,所以a
与b
的最大公因数必然是b
与r
的最大公因数;
若b
为0
,则最大公因数为a
。
所以,上方递推公式成立。
(31-40)素数
题面
解法:Eratosthenes筛法
Eratosthenes筛法,又名埃氏筛,是一种获取[1, n]
之内所有质数的算法,它通过遍历,将指定一个数的所有倍数从集合中排除,来筛选没有平凡约数的数作为质数,以下给出埃氏筛的C++实现代码。
1 | vector<int> primes, vis; |
正确性说明:任何一个合数
n
总存在介于(1, n)
内的至少一个因数,当循环执行到i = n
之前,必然会有i = j (j < n)
的循环情况将其排除出质数列表。
以此为基础,给出本题题解如下:
1 |
|
扩展
所有大于3
的素数都可以表示为6n + 1
或6n - 1
的形式,所以所有可以被表示为6n + 2
6n + 3
6n + 4
6n
形式的整数一定不是素数,可以用该方案快速判定一个数是否为质数,不过该算法对于优化埃氏筛没有太大帮助。
(21-30)阶乘倍数
题面
解法:分解因数
如果应用暴力方案的话,那么暂存的阶乘结果会很快越界,所以我们采取其他的方案。在这里我们做分解因数的方法。
我们从i = 2
开始递增循环,对于目标数target
,每一次循环都计算gcd(i, target)
,并将target
除去gcd(i, target)
。不断循环直到target
被除尽(即target == 1
),此时的target
经过了2, 3, 4 ... i
的所有因数的相约,所以target
现在就是i!
的约数之一,满足i! % target == 0
。
以下是示例代码:
1 |
|
(41-50)素数筛选法
题面
;
解法:Euler筛法
Euler筛法是Eratosthenes筛法的升级版,在Eratosthenes筛法中,一个拥有多个约数的合数实际上被筛选了多遍,如果我们可以让一个合数只被其最小的那个约数筛选一遍的话,我们就能提高筛选的效率,这样的话,我们引入Euler筛法:
1 | bool[] eulerGetPrimes(int target) { |
Euler筛法中,若当前用来筛选的约数i
可以整除已知质数表中用作倍数的p
,则此时i
乘上质数表中其它的质数也一定是p
的倍数,会被p
筛去,所以无需继续筛除,执行break;
退出。
由此给出本题题解:
1 |
|
(31-40)运动会
题面
解法:Euler函数
容易想到,去除队长本人(0, 0)
不可见,(1, 0)
可见和(0, 1)
可见以外,对于任意其它队员(x, y)
,其能被看见的充要条件是x
与y
互质(这样就不存在(x / a, y / a)
(a
为大于1
的正整数)的队员挡住他)。
所以,我们实际上就是要找出(x, y)
中x
y
互质的人有几个,这里使用gcd(int a, int b)
一个一个遍历的话会超时,我们为了过题,在此引入Euler函数
,它的功能是计算[1,x]
中与x互质的数的数量。
Euler函数的C++实现如下:
1 | int eulerPhi(int n) { |
Euler函数的运行原理如下:
- 设置答案
ans
为n
,代表[1, n]
区间内有n
个数。 - 现在我们把不互质的数从中去除,开始从
i = 2
循环,如果i ^ 2 <= n
而且n % i == 0
,那么执行ans -= ans / i;
,这一步骤主要是为了将n
的约数之一的i
的所有倍数从[1, n]
中划去,这样的数一共有ans / i
个。 - 在去除了这些数之后,由于
i
作为约数已经执行了删除,所以我不希望过会执行到2i
或者其它时i
的整数倍作为约数时再删除一遍,所以我执行while (n % i == 0) n /= i;
,将i
及其整数倍的约数从n
中除去,这样n % i == 0
的限制条件就会避免这些情况。 - 如此循环直到跳出,如果
n
不为1,说明其还存在未除去的约数(这是由于剩下的n
本身就是个质数),再做一轮去除。 - 返回
ans
,计算完成。
基于此,我们给出本题题解:
1 |
|
后记
本篇文章知识点:
- Eratosthenes筛法
- 质因数分解
- Euler筛法
- Euler函数
推荐阅读: