第11章:数学与数论
数学是算法的基石。在算法竞赛中,总有一些题目在“暴力”的外衣下,隐藏着一个数学或数论的内核。能否看穿这一点,并用高效的工具解决它,往往是区分“暴力选手”和“技巧选手”的关键。本章将聚焦于竞赛中最常遇到的数论与数学问题,并展示Python如何利用其丰富的标准库和语言特性,将复杂的数学计算“降维打击”为几行优雅的代码。
11.1 质数与约数:筛法、math.gcd, math.lcm
质数和约数是数论问题的起点,几乎贯穿了数论的全领域。
1. 质数 (Prime Number)
质数,又称素数,是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
-
判断单个质数:试除法
最直观的方法是试除法:检查从2到 N\sqrt{N}N 的所有整数能否整除 NNN。如果都不能,那么 NNN 就是质数。这个方法的时间复杂度为 O(N)O(\sqrt{N})O(N),适用于 NNN 比较大但判断次数不多的情况。import math def is_prime(n: int) -> bool: """判断一个数是否为质数""" if n < 2: return False for i in range(2, int(math.sqrt(n)) + 1): if n % i == 0: return False return True # 示例 print(f"13 is prime: { is_prime(13)}") # True print(f"20 is prime: { is_prime(20)}") # False -
批量生成质数:埃氏筛 (Sieve of Eratosthenes)
当需要找出一定范围(例如 111 到 10610^6106)内所有质数时,O(N)O(\sqrt{N})O(N) 的试除法会超时。此时,更高效的策略是“筛法”。埃氏筛是其中最经典、最常用的算法。其思想是:从2开始,将每个质数的倍数都标记为合数。一个数如果没有被前面的质数标记过,那它一定是质数。
def prime_sieve(n: int) -> list[bool]: """ 埃氏筛法,返回一个布尔列表,is_prime[i]为True表示i是质数。 时间复杂度: O(N log log N) """ is_prime = [True] * (n + 1) is_prime[0] = is_prime[1] = False # 0和1不是质数 for i in range(2, int(n**0.5) + 1): if is_prime[i]: # i的倍数(从i*i开始)都不是质数 for j in range(i * i, n + 1, i): is_prime[j] = False return is_prime # 找出100以内的所有质数 limit = 100 primes_bool = prime_sieve(limit) prime_numbers = [i for i, is_p in enumerate(primes_bool) if is_p] print(f"Primes up to { limit}: { prime_numbers}") # 输出: Primes up to 100: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]埃氏筛的代码简洁,效率极高,足以应对绝大多数竞赛场景。
2. 约数 (Divisor)
-
最大公约数 (GCD) 和 最小公倍数 (LCM)
在C++中,你可能需要自己手写欧几里得算法(辗转相除法)来求GCD。但在Python中,这完全是降维打击。math库已经为我们提供了现成、高效的函数。math.gcd(a, b): 计算a和b的最大公约数。math.lcm(a, b): 计算a和b的最小公倍数 (Python 3.9+)。
import math a, b = 48, 18 # 最大公约数 gcd_val = math.gcd(a, b) print(f"GCD

最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



