本文最后更新于 2025-04-12T00:00:06+08:00
质数
如果一个数只能被1 1 1 和它本身整除,那么这个数就称为质数,又称素数,否则称为合数.
判断一个数是否为质数
试除法判断一个数是否为质数
注意到如果有n m o d i = 0 n \bmod i = 0 n mod i = 0 ,则n = i × c n = i\times c n = i × c ,其中c = n i c = \frac{n}{i} c = i n 为一非零整数。换句话说,如果有n m o d i = 0 n \bmod i = 0 n mod i = 0 ,则一定有唯一的c c c 使得c × i = n c \times i = n c × i = n .
不妨称< i , c > <i, c> < i , c > 是n n n 的一个因子对,则在i ∈ [ 2 , n ] i \in [2, n] i ∈ [ 2 , n ] 中,如果< i , c > <i, c> < i , c > 是n n n 的一个因子对,由乘法交换律知< c , i > <c, i> < c , i > 也一定是n n n 的一个因子对,反过来,如果< i , c > <i, c> < i , c > 不是n n n 的一个因子对,则< c , i > <c, i> < c , i > 也一定不是n n n 的一个因子对。不妨假设i < n i < n i < n ,则我们只需判断在i ∈ [ 2 , ⌊ n i ⌋ ] i \in [2, \lfloor\frac{n}{i}\rfloor] i ∈ [ 2 , ⌊ i n ⌋] 中,n m o d i = 0 n \bmod i = 0 n mod 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) O ( n )
分解质因数
由质数的定义我们知道,每一个数都可以分解为若干质数的乘积。即
n = p 1 α 1 ⋅ p 2 α 2 ⋅ p 3 α 3 ⋯ p k α k n = p_1^{\alpha_1} \cdot p_2^{\alpha_2} \cdot p_3^{\alpha_3} \cdots p_k^{\alpha_k}
n = p 1 α 1 ⋅ p 2 α 2 ⋅ p 3 α 3 ⋯ p k α k
其中p i p_i p i 表示第i i i 个质数,α i \alpha_i α i 表示在这个分解因式中第i i i 个质数出现的次数(即它的幂次)。分解质因数问题需要我们求出给定数n n n 的所有质数和它们的幂次.
试除法分解质因数
通过枚举i ∈ [ 2 , n ] i \in [2, n] i ∈ [ 2 , n ] 中的每一个数,并判断每个数是否为质因子。该代码能够枚举出所有可能的质因子,而不会出现任何的合数。我们可以用反证法来证明这一点:
假设一个合数i ( ≥ 2 ) i(\ge 2) i ( ≥ 2 ) 满足n m o d i = 0 n \bmod i = 0 n mod i = 0 ,则一定有合数i i i 的一个质因子p p p 满足n m o d p = 0 n \bmod p = 0 n mod p = 0 ,且p < i p < i p < i 严格成立,由于i i i 是从小到大枚举的,故原假设不成立.
在这份代码中我们需要特判,如果n n n 本身就是质数,则它的所有质因子只有它本身,幂次为1 1 1 .
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 while n % i == 0 : n //= i s += 1 print (i, s) i += 1 if n > 1 : print (n, 1 )
时间复杂度为O ( n log n ) O(n \log n) O ( n log n )
筛质数
给定一个数n n n ,求出不大于n n n 的数中的所有质数。
朴素法筛质数
我们可以遍历不大于n n n 的每一个数,并判断这个数是否是质数。在判断之前,我们可以筛去所有的合数。
简单来说,当我们枚举到第j j j 个数的时候,如果这个数能够被一个质数整除,那么这个数就一定是合数。注意到如果存在j m o d i = 0 j \bmod i = 0 j mod i = 0 ,则i < = j , ( j ≥ 0 ) i <= j,(j \ge 0) i <= j , ( j ≥ 0 ) ,事实上我们可以在存储第i i i 个数时就把第j j j 个数筛去,因为第j j j 个数是以i i i 为因子的一个合数。只需添加更新代码,当j ∈ ( i , n ] j \in (i,n] j ∈ ( i , n ] 且j m o d i = 0 j \bmod i = 0 j mod i = 0 时,j j j 一定不是质数,执行语句st[j] = True
.
事实上,我们可以保证在更新过程中,i i i 一定是一个质数。这是因为每一个合数都可以由比它小的质数相乘得来,而枚举始终是从小到大的顺序,如果第j j j 个数是合数,在枚举到它之前,它就已经被筛去了。
1 2 3 4 5 6 7 8 9 10 primes = [0 ] st = [False ]*n 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 ( n log log n ) O(n\log\log n) O ( n log log n )
线性筛法筛质数
在朴素筛法中,对于每一个数,我们都将以该数作为因子的所有数筛去,但显然这样存在重复的操作。我们可以进一步优化代码。我们重新规定筛法:如果一个数可以被筛,那么它一定是被它的最小质因子筛去的 。这样做可以确保每个数只被筛一次。
如何保证每个合数只被最小质因子筛去呢?假设当前枚举到第i i i 个数
如果这个数没有被筛去,则存入p r i m e s primes p r im es 数组
遍历当前存在的每一个质数
如果i % primes[j] == 0
成立,说明p r i m e s [ j ] primes[j] p r im es [ j ] 是i i i 的最小质因子(因为j j j 是从小到大遍历的),同时p r i m e s [ j ] primes[j] p r im es [ j ] 是p r i m e s [ j ] ∗ i primes[j]*i p r im es [ j ] ∗ i 的最小质因子
否则,说明p r i m e s [ j ] primes[j] p r im es [ j ] 比i i i 的最小质因子要小,同时p r i m e s [ j ] primes[j] p r im es [ j ] 是p r i m e s [ j ] ⋅ i primes[j]\cdot i p r im es [ j ] ⋅ i 的最小质因子
执行st[primes[j] * i - 1] = True
,因为无论如何p r i m e s [ j ] ⋅ i primes[j] \cdot i p r im es [ j ] ⋅ i 的最小质因子都是p r i m e s [ j ] primes[j] p r im es [ j ]
如何证明这个算法能筛去所有合数?因为我们确保了每一个数的筛与不筛都是从小到大的顺序,如果一个数i = x i = x i = x 是合数,那么它一定满足x % primes[j] == 0
,其中p r i m e s [ j ] primes[j] p r im es [ j ] 是i i i 的最小质因子,令$ x \div primes[j] = m$ ,必定有 m < x m < x m < x ,即该合数一定会在i = m i = m i = 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 ) for i in range (2 , n + 1 ): if not isCombine[i]: primes.append(i) j = 0 while i <= n // primes[j]: isCombine[primes[j] * i] = True if i % primes[j] == 0 : break j += 1 return primes
时间复杂度为O ( n log log n ) O(n\log \log n) O ( n log log n )
约数
求一个数的所有约数
与求质数类似,对于一个整数n n n ,如果存在i ∈ [ 1 , n ] i \in [1, n] i ∈ [ 1 , n ] 有n m o d i = 0 n \bmod i = 0 n mod i = 0 ,则i i i 是n n n 的一个约数,同时n ÷ i n \div i n ÷ i 也是n n n 的一个约数,因此我们只需要枚举到i < = n i i <= \frac{n}{i} i <= i n 即可.
注意存入约数时如果存在i ∗ i = n i * i = n i ∗ i = n ,则i i i 应该只被存储一次,所以在每次存入n i \frac{n}{i} i n 时,需要特判该数是否与i i i 相等.
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) O ( n )
约数个数
给定一个整数n n n ,求出它所有约数的个数。
在分解质因数中,我们已经知道,对于整数n n n ,它一定可以分解成若干个质数的乘积,也即:
n = p 1 α 1 ⋅ p 2 α 2 ⋅ p 3 α 3 ⋯ p k α k , n = p_1^{\alpha_1} \cdot p_2^{\alpha_2} \cdot p_3^{\alpha_3} \cdots p_k^{\alpha_k},
n = p 1 α 1 ⋅ p 2 α 2 ⋅ p 3 α 3 ⋯ p k α k ,
注意到对于n n n 的第j j j 个约数d j d_j d j ,我们可以将其表示成
d j = p 1 β 1 ⋅ p 2 β 2 ⋅ p 3 β 3 ⋯ p k β k , 0 ≤ β i ≤ α i d_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
d j = p 1 β 1 ⋅ p 2 β 2 ⋅ p 3 β 3 ⋯ p k β k , 0 ≤ β i ≤ α i
并且由于是质数的乘积,对于任意两组不同的β 1 , β 2 β 3 , ⋯ β k \beta_1,\beta_2\,\beta_3,\cdots \beta_k β 1 , β 2 β 3 , ⋯ β k ,它们所组成的约数也一定不同。于是求约数的个数可以转化为求这样一个排列的组合,对于第i i i 项,β i \beta_i β i 可取[ 0 , α i ] [0, \alpha_i] [ 0 , α i ] 中的任意一个数,共α i + 1 \alpha_i + 1 α i + 1 种可能。根据分步乘法原理,所有约数的个数c n t cnt c n t 满足
c n t = ( α 1 + 1 ) ( α 2 + 1 ) ( α 3 + 1 ) ⋯ ( α k + 1 ) . cnt = (\alpha_1 + 1)(\alpha_2 + 1)(\alpha_3 + 1)\cdots(\alpha_k + 1).
c n t = ( α 1 + 1 ) ( α 2 + 1 ) ( α 3 + 1 ) ⋯ ( α 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 () 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 )) for p, a in cnti: ans *= a+1 return cnti, ans
时间复杂度为O ( n ) O(\sqrt n) O ( n ) .
约数之和
给定一个整数n n n ,求出它所有约数的和。
由上述约数性质可知,对于每一个组合p 1 β 1 , p 2 β 1 , p 1 β 1 , ⋯ p 1 β 1 p_1^{\beta_1},p_2^{\beta_1},p_1^{\beta_1},\cdots p_1^{\beta_1} p 1 β 1 , p 2 β 1 , p 1 β 1 , ⋯ p 1 β 1 ,它能够构成n n n 的一个约数,我们只需要将所有的约数相加即可。即约数之和S u m Sum S u m 满足
S u m = ∏ j = 1 k ∑ β i = 1 α i p j β 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]
S u m = j = 1 ∏ k β i = 1 ∑ α i p j β j , β i ∈ [ 1 , α i ]
也即
S u m = ( p 1 0 + p 1 1 + p 1 2 + ⋯ + p 1 α 1 ) ⋯ ( p k 0 + p k 1 + p k 2 + ⋯ + p k α 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}).
S u m = ( p 1 0 + p 1 1 + p 1 2 + ⋯ + p 1 α 1 ) ⋯ ( p k 0 + p k 1 + p k 2 + ⋯ + p k α 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): t = t*p + 1 res *= t return res
时间复杂度为O ( n ) O(\sqrt n) O ( n ) .
辗转相除法求最大公约数
给定两个数n , m n, m n , m ,求出它们的最大公约数。
性质:如果d ∣ a d | a d ∣ a 且d ∣ b d | b d ∣ b ,那么d ∣ ( a x + b y ) d | (ax+by) d ∣ ( a x + b y ) ,其中x , y x,y x , y 是整数,则有结论:a a a 和b b b 的最大公约数g c d ( a , b ) gcd(a, b) g c d ( a , b ) 等价于b b b 和a m o d b a \bmod b a mod b 的最大公约数g c d ( b , a m o d b ) gcd(b, a \bmod b) g c d ( b , a mod b ) 。
证明:设g c d ( a , b ) = d gcd(a,b) = d g c d ( a , b ) = d ,a m o d b = a − c ⋅ b a \bmod b = a - c\cdot b a mod b = a − c ⋅ b ,其中c c c 为一整数。则有d ∣ a , d ∣ b d|a,d|b d ∣ a , d ∣ b ,g c d ( b , a m o d b ) = g c d ( b , a − c ⋅ b ) gcd(b, a \bmod b) = gcd(b, a - c\cdot b) g c d ( b , a mod b ) = g c d ( b , a − c ⋅ b ) ,由性质可知,d ∣ ( a − c ⋅ b ) d | (a-c\cdot b) d ∣ ( a − c ⋅ b ) ,因此g c d ( a , b ) = g c d ( b , a − c ⋅ b ) gcd(a, b) = gcd(b, a-c\cdot b) g c d ( a , b ) = g c d ( b , a − c ⋅ b ) .
另一方面,如果g c d ( a , b ) = d gcd(a,b) = d g c d ( a , b ) = d ,则d ∣ a , d ∣ b d|a,d|b d ∣ a , d ∣ b ,则有d ∣ ( a x + b y ) d | (ax+by) d ∣ ( a x + b y ) ,令x = 0 , y = − c x = 0,y = -c x = 0 , y = − c ,则d ∣ ( a − c ⋅ b ) d|(a- c\cdot b) d ∣ ( a − c ⋅ b ) ,即g c d ( b , a − c ⋅ b ) = g c d ( a , b ) = d gcd(b, a-c\cdot b) = gcd(a, b) = d g c d ( b , a − c ⋅ b ) = g c d ( a , b ) = d .
证毕
1 2 def gcd (a, b ): return gcd(b, a % b) if b > 0 else a