约数(因数)

· · 算法·理论

约数(因数)

本文部分内容参考李煜东《算法竞赛进阶指南》

更新记录:

概述

::::info[定义] 若整数 n 除以整数 d 的余数为 0,即 d 能整除 n,则称 dn 的约数(或因数),nd 的倍数,记为 d \mid n。 ::::

::::info[算术基本定理的推论]{open} 设正整数 N 被唯一分解为:

N = p_1^{c_1} p_2^{c_2} \cdots p_m^{c_m}

其中 c_i 为正整数,p_i 为质数,且 p_1 < p_2 < \cdots < p_m

N 的正约数集合可表示为:

\{ p_1^{b_1} p_2^{b_2} \cdots p_m^{b_m} \}, \quad 0 \le b_i \le c_i

一、求约数

1. 试除法(求 N 的正约数集合)

d \ge \sqrt{N}N 的约数,则 N/d \le \sqrt{N} 也是 N 的约数。也就是说,约数总是成对出现的(对于完全平方数,\sqrt{N} 会单独出现)。

因此,只需扫描 d = 1 \sim \lfloor \sqrt{N} \rfloor,判断 d 是否能整除 N。若能,则 dN/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}),过高。

换一种思路:对于每个数 d1 \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))$,是求最大公约数最常用的方法。不过,由于高精度除法(取模)实现较为复杂,在需要高精度运算的场景下,可以考虑使用更相减损术替代。