质数及数的质分解

质数

如果一个数只能被11和它本身整除,那么这个数就称为质数,又称素数,否则称为合数.

判断一个数是否为质数

试除法判断一个数是否为质数

注意到如果有nmodi=0n \bmod i = 0,则n=i×cn = i\times c,其中c=nic = \frac{n}{i}为一非零整数。换句话说,如果有nmodi=0n \bmod i = 0,则一定有唯一的cc使得c×i=nc \times i = n.

不妨称<i,c><i, c>nn的一个因子对,则在i[2,n]i \in [2, n]中,如果<i,c><i, c>nn的一个因子对,由乘法交换律知<c,i><c, i>也一定是nn的一个因子对,反过来,如果<i,c><i, c>不是nn的一个因子对,则<c,i><c, i>也一定不是nn的一个因子对。不妨假设i<ni < n,则我们只需判断在i[2,ni]i \in [2, \lfloor\frac{n}{i}\rfloor]中,nmodi=0n \bmod i = 0是否成立即可.

1
2
3
4
5
6
7
8
9
def is_prime(n):
if n < 2:
return False
i = 2
while i <= n // i:
if n % i == 0:
return False
i += 1
return True

时间复杂度恒为O(n)O(\sqrt n)

分解质因数

由质数的定义我们知道,每一个数都可以分解为若干质数的乘积。即

n=p1α1p2α2p3α3pkαkn = p_1^{\alpha_1} \cdot p_2^{\alpha_2} \cdot p_3^{\alpha_3} \cdots p_k^{\alpha_k}

其中pip_i表示第ii个质数,αi\alpha_i表示在这个分解因式中第ii个质数出现的次数(即它的幂次)。分解质因数问题需要我们求出给定数nn的所有质数和它们的幂次.

试除法分解质因数

通过枚举i[2,n]i \in [2, n]中的每一个数,并判断每个数是否为质因子。该代码能够枚举出所有可能的质因子,而不会出现任何的合数。我们可以用反证法来证明这一点:

假设一个合数i(2)i(\ge 2)满足nmodi=0n \bmod i = 0,则一定有合数ii的一个质因子pp满足nmodp=0n \bmod p = 0,且p<ip < i严格成立,由于ii是从小到大枚举的,故原假设不成立.

在这份代码中我们需要特判,如果nn本身就是质数,则它的所有质因子只有它本身,幂次为11.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def division(n):
i = 2
while i <= n // i:
# 从小到大枚举每一个质数
if n % i == 0:
# 判断是否为质因子,避免输出s为0的情况
s = 0
while n % i == 0:
# 计算该质因子的幂次
n //= i
s += 1
print(i, s)
i += 1
if n > 1:
# 如果n本身就是一个质数
print(n, 1)

时间复杂度为O(nlogn)O(n \log n)

筛质数

给定一个数nn,求出不大于nn的数中的所有质数。

朴素法筛质数

我们可以遍历不大于nn的每一个数,并判断这个数是否是质数。在判断之前,我们可以筛去所有的合数。

简单来说,当我们枚举到第jj个数的时候,如果这个数能够被一个质数整除,那么这个数就一定是合数。注意到如果存在jmodi=0j \bmod i = 0,则i<=j,(j0)i <= j,(j \ge 0),事实上我们可以在存储第ii个数时就把第jj个数筛去,因为第jj个数是以ii为因子的一个合数。只需添加更新代码,当j(i,n]j \in (i,n]jmodi=0j \bmod i = 0时,jj一定不是质数,执行语句st[j] = True.

事实上,我们可以保证在更新过程中,ii一定是一个质数。这是因为每一个合数都可以由比它小的质数相乘得来,而枚举始终是从小到大的顺序,如果第jj个数是合数,在枚举到它之前,它就已经被筛去了。

1
2
3
4
5
6
7
8
9
10
primes = [0] # 用于存储所有的质数
st = [False]*n # 储存第i个数被筛的状态,初始时均未被筛

def get_primes(n):
for i in range(2, n+1):
if st[i-1]:
continue
primes.append(i)
for j in range(i+i, n+1, i):
st[j-1] = True

时间复杂度约为O(nloglogn)O(n\log\log n)

线性筛法筛质数

在朴素筛法中,对于每一个数,我们都将以该数作为因子的所有数筛去,但显然这样存在重复的操作。我们可以进一步优化代码。我们重新规定筛法:如果一个数可以被筛,那么它一定是被它的最小质因子筛去的。这样做可以确保每个数只被筛一次。

如何保证每个合数只被最小质因子筛去呢?假设当前枚举到第ii个数

  • 如果这个数没有被筛去,则存入primesprimes数组
  • 遍历当前存在的每一个质数
    • 如果i % primes[j] == 0成立,说明primes[j]primes[j]ii的最小质因子(因为jj是从小到大遍历的),同时primes[j]primes[j]primes[j]iprimes[j]*i的最小质因子
    • 否则,说明primes[j]primes[j]ii的最小质因子要小,同时primes[j]primes[j]primes[j]iprimes[j]\cdot i的最小质因子
  • 执行st[primes[j] * i - 1] = True,因为无论如何primes[j]iprimes[j] \cdot i的最小质因子都是primes[j]primes[j]

如何证明这个算法能筛去所有合数?因为我们确保了每一个数的筛与不筛都是从小到大的顺序,如果一个数i=xi = x是合数,那么它一定满足x % primes[j] == 0,其中primes[j]primes[j]ii的最小质因子,令$ x \div primes[j] = m$ ,必定有 m<xm < x ,即该合数一定会在i=mi = m时执行语句st[primes[j] * m - 1] = True,则该合数一定会被筛去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def get_primes(n):
primes = [] # 用于存储所有的质数
isCombine = [False] * (n + 1)
# 储存第i个数被筛的状态,初始时均未被筛
for i in range(2, n + 1):
# 如果当前数是质数
if not isCombine[i]: primes.append(i)
j = 0
while i <= n // primes[j]:
# primes[j] * i 一定是合数
isCombine[primes[j] * i] = True
if i % primes[j] == 0:
# i的最小质因子是primes[j],不能再筛
break
# 筛下一个质数
j += 1
return primes

时间复杂度为O(nloglogn)O(n\log \log n)

约数

求一个数的所有约数

与求质数类似,对于一个整数nn,如果存在i[1,n]i \in [1, n]nmodi=0n \bmod i = 0,则iinn的一个约数,同时n÷in \div i也是nn的一个约数,因此我们只需要枚举到i<=nii <= \frac{n}{i}即可.

注意存入约数时如果存在ii=ni * i = n,则ii应该只被存储一次,所以在每次存入ni\frac{n}{i}时,需要特判该数是否与ii相等.

1
2
3
4
5
6
7
8
9
10
def get_divisons(n):
ans = [] # 记录所有的约数
i = 1
while i <= n // i:
if n % i == 0:
ans.append(i)
if i != n // i:
ans.append(n // i)
i += 1
return sorted(ans)

对结果排序即可得到有序的约数集,但由于一个数的约数数量远比这个数本身小得多,最终时间复杂度为O(n)O(\sqrt n)

约数个数

给定一个整数nn,求出它所有约数的个数。

在分解质因数中,我们已经知道,对于整数nn,它一定可以分解成若干个质数的乘积,也即:

n=p1α1p2α2p3α3pkαk,n = p_1^{\alpha_1} \cdot p_2^{\alpha_2} \cdot p_3^{\alpha_3} \cdots p_k^{\alpha_k},

注意到对于nn的第jj个约数djd_j,我们可以将其表示成

dj=p1β1p2β2p3β3pkβk,0βiαid_j = p_1^{\beta_1} \cdot p_2^{\beta_2} \cdot p_3^{\beta_3} \cdots p_k^{\beta_k}, \quad\quad 0 \le \beta_i \le \alpha_i

并且由于是质数的乘积,对于任意两组不同的β1,β2β3,βk\beta_1,\beta_2\,\beta_3,\cdots \beta_k,它们所组成的约数也一定不同。于是求约数的个数可以转化为求这样一个排列的组合,对于第ii项,βi\beta_i可取[0,αi][0, \alpha_i]中的任意一个数,共αi+1\alpha_i + 1种可能。根据分步乘法原理,所有约数的个数cntcnt满足

cnt=(α1+1)(α2+1)(α3+1)(αk+1).cnt = (\alpha_1 + 1)(\alpha_2 + 1)(\alpha_3 + 1)\cdots(\alpha_k + 1).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def division_cnt(n):
cnti = set() # 保存每一个质数和它的幂次
# n本身就是它的一个约数
ans = 1
i = 2
while i <= n // i:
# 保存当前质数的幂次
cnt = 0
while n % i == 0:
cnt += 1
n //= i
cnti.add((i, cnt))
i += 1

# 需要特判,如果最终n不是1,说明这个数是一个质数
# (且为n的所有约数中最大的有一个)
if n > 1:
cnti.add((n, 1))
for p, a in cnti:
ans *= a+1
return cnti, ans

时间复杂度为O(n)O(\sqrt n).

约数之和

给定一个整数nn,求出它所有约数的和。

由上述约数性质可知,对于每一个组合p1β1,p2β1,p1β1,p1β1p_1^{\beta_1},p_2^{\beta_1},p_1^{\beta_1},\cdots p_1^{\beta_1},它能够构成nn的一个约数,我们只需要将所有的约数相加即可。即约数之和SumSum满足

Sum=j=1kβi=1αipjβj,βi[1,αi]Sum = \prod_{j = 1}^{k}\sum_{\beta_i = 1}^{\alpha_i} p_j^{\beta_j}, \quad\quad\beta_i \in [1, \alpha_i]

也即

Sum=(p10+p11+p12++p1α1)(pk0+pk1+pk2++pkαk).Sum = (p_1^0 + p_1^1 + p_1^2 +\cdots + p_1^{\alpha_1})\cdots(p_k^0 + p_k^1 + p_k^2 +\cdots + p_k^{\alpha_k}).

对此如有疑惑,将上式展开则豁然开朗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def division(n):
cnti = set()
ans = 1
i = 2
while i <= n // i:
cnt = 0
while n % i == 0:
cnt += 1
n //= i
cnti.add((i, cnt))
i += 1

if n > 1:
cnti.add((n, 1))
res = 1
for p, a in cnti:
t = 1
for _ in range(a):
# 求(p^0 + p^1 +... + p^a)
t = t*p + 1
res *= t
return res

时间复杂度为O(n)O(\sqrt n).

辗转相除法求最大公约数

给定两个数n,mn, m,求出它们的最大公约数。

性质:如果dad | adbd | b,那么d(ax+by)d | (ax+by),其中x,yx,y是整数,则有结论:aabb的最大公约数gcd(a,b)gcd(a, b)等价于bbamodba \bmod b的最大公约数gcd(b,amodb)gcd(b, a \bmod b)

证明:设gcd(a,b)=dgcd(a,b) = damodb=acba \bmod b = a - c\cdot b,其中cc 为一整数。则有da,dbd|a,d|bgcd(b,amodb)=gcd(b,acb)gcd(b, a \bmod b) = gcd(b, a - c\cdot b),由性质可知,d(acb)d | (a-c\cdot b),因此gcd(a,b)=gcd(b,acb)gcd(a, b) = gcd(b, a-c\cdot b).

另一方面,如果gcd(a,b)=dgcd(a,b) = d,则da,dbd|a,d|b,则有d(ax+by)d | (ax+by),令x=0,y=cx = 0,y = -c,则d(acb)d|(a- c\cdot b),即gcd(b,acb)=gcd(a,b)=dgcd(b, a-c\cdot b) = gcd(a, b) = d.

证毕

1
2
def gcd(a, b):
return gcd(b, a % b) if b > 0 else a

质数及数的质分解
https://nhwite.cn/质数及数的质分解/
作者
重言
发布于
2025年4月11日
许可协议