若 d \ge \sqrt{N} 是 N 的约数,则 N/d \le \sqrt{N} 也是 N 的约数。也就是说,约数总是成对出现的(对于完全平方数,\sqrt{N} 会单独出现)。
因此,只需扫描 d = 1 \sim \lfloor \sqrt{N} \rfloor,判断 d 是否能整除 N。若能,则 d 和 N/d 都是 N 的约数。时间复杂度为 O(\sqrt{N})。
::::info[代码实现]
int factor[1600], m = 0;
for (int i = 1; i * i <= n; i++) {
if (n % i == 0) {
factor[++m] = i;
if (i != n / i) factor[++m] = n / i;
}
}
for (int i = 1; i <= m; i++) cout << factor[i] << endl;
::::
::::info[试除法的推论]{open}
一个整数 N 的约数个数上界为 2\sqrt{N}。
::::
2. 倍数法(求 1 \sim N 中每个数的正约数集合)
如果用试除法分别求出 1 \sim N 中每个数的正约数集合,时间复杂度为 O(N\sqrt{N}),过高。
换一种思路:对于每个数 d,1 \sim N 中以 d 为约数的数正是 d 的倍数:d, 2d, 3d, \dots, \lfloor N/d \rfloor \times d。于是我们可以枚举 d,将其加入这些倍数的约数集合中。这就是“倍数法”。
::::info[代码实现]
vector<int> factor[500010];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n / i; j++)
factor[i * j].push_back(i);
for (int i = 1; i <= n; i++) {
for (int j = 0; j < factor[i].size(); j++)
printf("%d ", factor[i][j]);
puts("");
}
::::
上述算法的时间复杂度为:
O(N + N/2 + N/3 + \cdots + N/N) = O(N \log N)
::::info[倍数法的推论]{open}
::::
---
### 3. 习题
- [反素数](https://www.luogu.com.cn/problem/P1463)
- [余数之和](https://www.luogu.com.cn/problem/P2261)
---
## 二、最大公约数与最小公倍数
### 1. 定义
::::info[定义]
- **最大公约数**:若自然数 $d$ 同时是自然数 $a$ 和 $b$ 的约数,则称 $d$ 为 $a$ 和 $b$ 的公约数。在所有公约数中最大的一个,称为 $a$ 和 $b$ 的最大公约数,记为 $\gcd(a, b)$。
- **最小公倍数**:若自然数 $m$ 同时是自然数 $a$ 和 $b$ 的倍数,则称 $m$ 为 $a$ 和 $b$ 的公倍数。在所有公倍数中最小的一个,称为 $a$ 和 $b$ 的最小公倍数,记为 $\operatorname{lcm}(a, b)$。
类似地,也可以定义多个数的最大公约数和最小公倍数。
::::
---
### 2. 基本定理
$$ \forall a, b \in \mathbb{N}, \quad \gcd(a, b) \cdot \operatorname{lcm}(a, b) = a \cdot b $$
::::success[证明]
设 $d = \gcd(a, b)$,令 $a_0 = a/d$,$b_0 = b/d$。由最大公约数的定义可知 $\gcd(a_0, b_0) = 1$。
根据最小公倍数的定义,有 $\operatorname{lcm}(a_0, b_0) = a_0 b_0$。
于是:
$$\operatorname{lcm}(a, b) = \operatorname{lcm}(a_0 d, b_0 d) = \operatorname{lcm}(a_0, b_0) \cdot d = a_0 b_0 d = \frac{ab}{d}$$
所以 $\gcd(a, b) \cdot \operatorname{lcm}(a, b) = ab$,得证。
::::
---
### 3. 更相减损术(九章算术)
$$ \forall a, b \in \mathbb{N}, a \ge b, \quad \gcd(a, b) = \gcd(b, a - b) = \gcd(a, a - b) $$
$$ \forall a, b \in \mathbb{N}, \quad \gcd(2a, 2b) = 2 \gcd(a, b) $$
::::success[证明]
这里主要证明前者。对于 $a, b$ 的任意公约数 $d$,由于 $d \mid a$ 且 $d \mid b$,所以 $d \mid (a - b)$。因此 $d$ 也是 $b$ 和 $a-b$ 的公约数。反之,若 $d$ 是 $b$ 和 $a-b$ 的公约数,则 $d \mid b$ 且 $d \mid (a-b)$,从而 $d \mid a$,故 $d$ 也是 $a$ 和 $b$ 的公约数。所以两个数对的公约数集合相同,最大公约数自然也相等。对于 $\gcd(a, a-b)$ 同理可证。后者由公因数性质显然成立。
::::
---
### 4. 欧几里得算法(辗转相除法)
$$ \forall a, b \in \mathbb{N}, b \neq 0, \quad \gcd(a, b) = \gcd(b, a \bmod b) $$
::::success[证明]
若 $a < b$,则 $\gcd(b, a \bmod b) = \gcd(b, a) = \gcd(a, b)$,命题成立。
若 $a \ge b$,设 $a = qb + r$,其中 $0 \le r < b$,显然 $r = a \bmod b$。对于 $a, b$ 的任意公约数 $d$,因为 $d \mid a$ 且 $d \mid qb$,所以 $d \mid (a - qb)$,即 $d \mid r$。因此 $d$ 也是 $b$ 和 $r$ 的公约数。反之亦成立。故二者的公约数集合相同,最大公约数相等。
::::
::::info[代码实现]
```cpp
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
```
::::
欧几里得算法的时间复杂度为 $O(\log(a + b))$,是求最大公约数最常用的方法。不过,由于高精度除法(取模)实现较为复杂,在需要高精度运算的场景下,可以考虑使用更相减损术替代。