1 问题:怎样找出一个数的全部因子
在 整除 中,我们已经知道:若正整数
例如
注意不要把「约数」和「质因数」混淆:
接下来分别解决两个问题:
- 给一个正整数
,列出它的全部正约数。 - 给一个上界
,预处理 每个数的全部正约数。
2 先从朴素枚举开始
正约数不可能大于 n % i == 0,就找到了一个约数。
这需要
有没有办法,不检查所有候选,也能找到全部约数?
3 单个数:约数总是成对出现
3.1 一次整除,得到两个约数
如果
这说明
| 较小的约数 | 配对的约数 | 乘积 |
|---|---|---|
| 1 | 36 | 36 |
| 2 | 18 | 36 |
| 3 | 12 | 36 |
| 4 | 9 | 36 |
| 6 | 6 | 36 |
当发现
3.2 为什么只需要检查到平方根
一对约数
因此,每对约数中至少有一个不超过
就能遇到每一对约数中的较小者,再通过
3.3 完全平方数:平方根只记录一次
对于
因此每次整除成功时:
- 记录
; - 计算
; - 仅当
时,才记录 。
d == n / d 是在已经确认整除的前提下,判断当前这一对约数是否重合,也就是当前是否正好枚举到平方根。不要理解成「只要 n 是完全平方数,每一对都只记录一个」。例如
3.4 如何直接得到升序结果
如果每找到一对就立即输出,顺序会是 1 36 2 18 ...,并非升序。
可以先把全部结果放在一个数组里,最后排序;也可以利用约数对本身的顺序,不额外排序:
- 小约数按枚举顺序进入
small,天然递增; - 大约数进入
large,因为随着 增大而减小,所以天然递减; - 最后把
large反转,接到small后面。
例如
small:1 2 3 4 6
large:36 18 12 9
反转 large:9 12 18 36
拼接结果:1 2 3 4 6 9 12 18 36
3.5 C++ / Python 实现
两份程序都读入一个正整数 long long 的正数范围内;Python 使用整数运算,不依赖浮点平方根。
#include <iostream>
#include <vector>
using namespace std;
vector<long long> get_factors(long long n) {
vector<long long> small, large;
// d <= n / d 等价于 d * d <= n,但不会发生乘法溢出。
for (long long d = 1; d <= n / d; d++) {
if (n % d != 0) continue;
small.push_back(d);
long long other = n / d;
if (d != other) large.push_back(other);
}
// 大约数原本递减,反向拼接后得到升序结果。
for (int i = (int)large.size() - 1; i >= 0; i--) {
small.push_back(large[i]);
}
return small;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long n;
if (!(cin >> n)) return 0;
vector<long long> factors = get_factors(n);
for (int i = 0; i < (int)factors.size(); i++) {
if (i > 0) cout << ' ';
cout << factors[i];
}
cout << '\n';
return 0;
}
import sys
def get_factors(n: int) -> list[int]:
small = []
large = []
d = 1
while d * d <= n:
if n % d == 0:
small.append(d)
other = n // d
if d != other:
large.append(other)
d += 1
# 小约数递增,大约数递减;反转后拼接。
small.extend(reversed(large))
return small
def main():
n = int(sys.stdin.buffer.readline())
print(*get_factors(n))
if __name__ == '__main__':
main()
输入:
36
输出:
1 2 3 4 6 9 12 18 36
C++ 用 d <= n / d 作为循环条件,而不是 d * d <= n,避免乘法溢出。对于正整数,这两个条件等价。Python 整数没有固定宽度溢出问题,可以直接使用 d * d <= n。
设
- 检查候选耗时
; - 拼接、输出耗时
; - 总时间为
,存储结果需要 空间。
这里没有额外排序,因此不会多出
4 批量预处理:反过来枚举倍数
4.1 从「谁是它的约数」到「它是谁的约数」
如果需要得到
换一个枚举方向:
不再问「
的约数有哪些」,而是问「 是哪些数的约数」。
答案很直接:
因此可以先建立 factors[0..N],然后:
- 从小到大枚举
; - 枚举
的所有倍数 ; - 把
加入 factors[j]。
例如外层枚举到
4.2 为什么不漏、不重,而且有序
如果
每个
由于外层
4.3 C++ / Python 实现
程序第一行读入
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int>> precompute_factors(int n) {
vector<vector<int>> factors(n + 1);
for (int d = 1; d <= n; d++) {
// 使用 long long 防止 j += d 时发生 int 溢出。
for (long long j = d; j <= n; j += d) {
factors[j].push_back(d);
}
}
return factors;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, q;
if (!(cin >> n >> q)) return 0;
vector<vector<int>> factors = precompute_factors(n);
for (int i = 0; i < q; i++) {
int x;
cin >> x;
for (int j = 0; j < (int)factors[x].size(); j++) {
if (j > 0) cout << ' ';
cout << factors[x][j];
}
cout << '\n';
}
return 0;
}
import sys
def precompute_factors(n: int) -> list[list[int]]:
# 为每个数创建独立列表,不能写成 [[]] * (n + 1)。
factors = [[] for _ in range(n + 1)]
for d in range(1, n + 1):
for j in range(d, n + 1, d):
factors[j].append(d)
return factors
def main():
# 按行读入并依次产生整数,避免一次保留所有查询和输出。
data = (int(value) for line in sys.stdin.buffer for value in line.split())
n = next(data)
q = next(data)
factors = precompute_factors(n)
for _ in range(q):
x = next(data)
print(*factors[x])
if __name__ == '__main__':
main()
输入:
12 3
1
6
12
输出:
1
1 2 3 6
1 2 3 4 6 12
下标
4.4 为什么复杂度是
外层枚举
因此预处理时间为
查到 factors[x] 这个列表只需
若只需要每个数的约数个数或约数和,可以把“加入列表”改成累加计数或累加
5 怎样选择方法
| 比较项 | 单个数的成对试除 | 全值域枚举倍数 |
|---|---|---|
| 适用情形 | 单次或少量查询,不必处理整个值域 | 值域不大,需要许多数的约数 |
| 时间 | 每个数 |
预处理 |
| 存储 | 单个数 |
全部列表 |
| 升序结果 | 小约数与大约数分开记录后拼接 | 天然升序 |
| 输出查询 |
不能只看到「查询多」就一定预处理。例如只有几百个数,但最大值达到
反过来,即使
单个
6 常见用途与边界
- 缩小答案范围:若某个候选答案必须整除
,只枚举约数,再逐个验证,不必检查所有整数。 - 统计整除关系:枚举一个数的约数,查询这些约数在频次表中出现了多少次。
- 完整重复块:若字符串由同一个长度为
的短串完整重复组成,则 ,可以只检查字符串长度的约数。注意一般意义下允许末尾不完整的“周期”不一定整除长度,例如 ababa有周期,但 。
实现时还要检查:
:约数只有 ,不能因为配对而输出两次。 为质数:约数只有 。 为完全平方数:平方根只记录一次,例如 的 。 - 只枚举正约数:不把
当作除数,也不把 套入本模板。 - 使用整数除法:Python 配对约数用
n // d,不是得到浮点数的n / d。
7 总结
找一个数的约数:枚举较小的一半,用
找一批数的约数:枚举约数
前者利用成对性减少候选,后者交换枚举方向复用整除关系。先确定需要处理多少个数、值域有多大、是否必须保存全部约数,再选择实现。
8 相关题解
暂无相关题解