设 f(n,lo) 为把 n 拆成不小于 lo 的因子之积的方案数,按首因子 d 分类递推 f(n,lo)=1+Σf(n/d,d)(d≤√n 且 d|n),记忆化即可
OJ: roj
题目 ID: 1200
难度:普及-
标签:搜索深度优先搜索记忆化搜索数学因数分解
创建: 2026-09-29 23:16
更新: 2026-10-07 12:15
题目描述
给出正整数 2 / 2 / 20 对应输出 1 / 4。
思路
设
参考代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-29 23:20
# update_at: 2026-09-29 23:20
import sys
from functools import cache
from math import isqrt
MIN_FACTOR = 2 # 题面要求每个因子 a_i 都大于 1;它同时是"选取下一个因子的下界"初值
@cache
def count(n: int, lo: int) -> int:
"""把 n 拆成若干个 >= lo 的因子之积(无序、因子可重复)的方案数。
`lo` 是当前这一位因子的下界:上一轮选了 d,剩下 n/d 的下界就是 d,
保证因子序列非降序,从而每个无序分解只被数一次。
结果含"单因子 n 本身"这一种分解,所以初值取 1。
"""
# 只枚举不超过 sqrt(n) 的首因子:再大的首因子会强迫第二位小于自己,违反非降序
return 1 + sum(count(n // d, d) for d in range(lo, isqrt(n) + 1) if n % d == 0)
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
T = next(data)
# 递归只在 n % d == 0 且 d <= isqrt(n) 时发生,于是 n//d >= d,调用点恒有 n >= lo;
# 题面又保证 a > 1,所以永远不会走到 n < lo 这种会多算 1 的边界。
out = [count(next(data), MIN_FACTOR) for _ in range(T)]
print('\n'.join(map(str, out)))
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-05 05:19
* update_at: 2026-10-05 05:19
*/
#include <cstdio>
const int MAXA = 32768; // a 的取值范围上界(题面:1 < a < 32768)
const int MAXLO = 182; // 因子下界 lo 不超过 sqrt(32767) = 181,留一点余量
typedef long long ll;
// memo[n][lo] = 把 n 拆成若干个不小于 lo 的因子之积的方案数;0 表示还没算过。
// 答案最大只有 1713(a = 30240 时),用 int 足够,可省一半内存。
int memo[MAXA][MAXLO];
// 求把 n 拆成若干不小于 lo 的因子之积(因子非降序、可重复)的方案数。
// 非降序写法让每个无序分解只被数一次,因此不需要 set 去重。
int count_ways(int n, int lo) {
if (memo[n][lo] != 0) return memo[n][lo]; // 记忆化:同一状态只算一次
int res = 1; // 初值 1 表示不再往下拆,正好对应题面的 a = a 这一种分解
// 非降序要求首因子 d 满足 d <= n/d,即 d*d <= n;d <= 181,int 乘法不会溢出
for (int d = lo; d * d <= n; ++d) {
if (n % d == 0) res += count_ways(n / d, d); // 选 d 后,剩下 n/d 的因子下界收紧到 d
}
memo[n][lo] = res;
return res;
}
int main() {
int t;
scanf("%d", &t);
for (int i = 1; i <= t; ++i) {
int a;
scanf("%d", &a);
printf("%d\n", count_ways(a, 2)); // 合法因子最小是 2
}
return 0;
}