题目描述
算术基本定理指出,每个大于 1 1 1 的整数都可以唯一地表示为一个或多个质数的乘积。尽管表示唯一,但质因子的排列方式可能有多种。例如:
10 = 2 × 5 或 5 × 2 20 = 2 × 2 × 5 或 2 × 5 × 2 或 5 × 2 × 2 10 = 2 \times 5 \quad \text{或} \quad 5 \times 2 \\ 20 = 2 \times 2 \times 5 \quad \text{或} \quad 2 \times 5 \times 2 \quad \text{或} \quad 5 \times 2 \times 2 10=2×5或5×220=2×2×5或2×5×2或5×2×2
定义 f ( k ) f(k) f(k) 为整数 k k k 的质因子所有不同排列的数量。因此 f ( 10 ) = 2 f(10) = 2 f(10)=2, f ( 20 ) = 3 f(20) = 3 f(20)=3。
给定一个正整数 n n n,总存在至少一个数 k k k 使得 f ( k ) = n f(k) = n f(k)=n。我们想要知道最小的这样的 k k k。
输入格式
输入最多包含
1000
1000
1000 组测试用例,每组一行。每个测试用例是一个正整数
n
<
2
63
n < 2^{63}
n<263。
输出格式
对于每个测试用例,输出其对应的
n
n
n 以及最小的
k
>
1
k > 1
k>1 使得
f
(
k
)
=
n
f(k) = n
f(k)=n。输入中的数字保证满足
k
<
2
63
k < 2^{63}
k<263。
样例输入
1
2
3
105
样例输出
1 2
2 6
3 12
105 720
题目分析
1. 理解 f ( k ) f(k) f(k) 的数学本质
设 k k k 的唯一质因数分解为:
k = p 1 a 1 ⋅ p 2 a 2 ⋯ p m a m k = p_1^{a_1} \cdot p_2^{a_2} \cdots p_m^{a_m} k=p1a1⋅p2a2⋯pmam
其中 p i p_i pi 是质数, a i a_i ai 是正整数。
所有质因子(考虑重复)的总数为:
S = a 1 + a 2 + ⋯ + a m S = a_1 + a_2 + \cdots + a_m S=a1+a2+⋯+am
这些因子的不同排列数是一个多重集合的排列数:
f ( k ) = S ! a 1 ! ⋅ a 2 ! ⋯ a m ! f(k) = \frac{S!}{a_1! \cdot a_2! \cdots a_m!} f(k)=a1!⋅a2!⋯am!S!
2. 问题转化
我们需要:对于给定的 n n n,找到最小的 k k k 使得:
S ! a 1 ! ⋅ a 2 ! ⋯ a m ! = n \frac{S!}{a_1! \cdot a_2! \cdots a_m!} = n a1!⋅a2!⋯am!S!=n
其中:
- a 1 ≥ a 2 ≥ ⋯ ≥ a m a_1 \ge a_2 \ge \dots \ge a_m a1≥a2≥⋯≥am(为了最小化 k k k)
- k = 2 a 1 ⋅ 3 a 2 ⋅ 5 a 3 ⋯ k = 2^{a_1} \cdot 3^{a_2} \cdot 5^{a_3} \cdots k=2a1⋅3a2⋅5a3⋯(使用最小的质数来最小化 k k k)
3. 关键观察
- 指数非递增:为了最小化 k k k,我们应让较大的指数对应较小的质数。
- 质数顺序:使用最小的质数 2 , 3 , 5 , 7 , … 2, 3, 5, 7, \dots 2,3,5,7,…。
- k k k 的构成: k = 2 a 1 × 3 a 2 × 5 a 3 × … k = 2^{a_1} \times 3^{a_2} \times 5^{a_3} \times \dots k=2a1×3a2×5a3×…
- n n n 的约束: n < 2 63 n < 2^{63} n<263,意味着 S S S 不会太大(因为 S ! S! S! 增长极快)。
4. 搜索策略
我们需要枚举所有可能的指数序列 ( a 1 , a 2 , … , a m ) (a_1, a_2, \dots, a_m) (a1,a2,…,am),计算对应的 n n n 和 k k k,并记录最小的 k k k。
4.1 状态定义
idx:当前正在考虑第几个质数(从 0 0 0 开始)lastExp:上一个质数的指数(保证指数非递增)currentVal:当前 k k k 的值(用unsigned long long存储)currentDiv:当前 n n n 的值(用__int128存储避免溢出)sumExp:当前总指数和 S S S
4.2 递归过程
- 基准情况:对于当前状态,如果
n
n
n 值合法(在
unsigned long long范围内),更新答案。 - 扩展状态:
- 选择下一个质数
primes[idx] - 选择指数
e( 1 ≤ e ≤ lastExp 1 \le e \le \texttt{lastExp} 1≤e≤lastExp) - 计算新的
k
k
k 值:
newVal = currentVal * (primes[idx]^e) - 计算新的
n
n
n 值:
newDiv = currentDiv * C(sumExp+e, e)
(因为增加 e e e 个相同质因子,排列数乘上组合数 C ( S + e , e ) C(S+e, e) C(S+e,e)) - 递归搜索
- 选择下一个质数
4.3 优化技巧
- 指数递减:强制要求 a 1 ≥ a 2 ≥ … a_1 \ge a_2 \ge \dots a1≥a2≥…,减少搜索空间。
- 提前剪枝:
- 当
currentVal或newVal超过 2 63 2^{63} 263 时停止 - 当
currentDiv或newDiv超过 2 63 2^{63} 263 时停止
- 当
- 使用
__int128:防止中间计算溢出。 - 预处理质数:使用前 16 16 16 个质数足够(因为 2 63 2^{63} 263 限制)。
5. 算法复杂度
- 状态数:受限于指数序列的数量,实际上非常有限(几百个状态)。
- 每个状态: O ( 1 ) O(1) O(1) 计算。
- 总体复杂度:可以接受。
代码实现
// Factors
// UVa ID: 1575
// Verdict: Accepted
// Submission Date: 2025-12-02
// UVa Run Time: 0.010s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
typedef __int128 int128_t;
const ULL INF = (1ULL << 63); // 上限 2^63
const int MAX_PRIME = 16; // 使用的质数个数
int primes[MAX_PRIME] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
map<ULL, ULL> ans; // 存储 n -> 最小 k
// 深度优先搜索枚举指数序列
void dfs(int idx, int lastExp, ULL currentVal, int128_t currentDiv, int sumExp) {
// 只有当 currentDiv 可以表示为 ULL 时才记录
if (currentDiv <= INF && currentDiv > 0) {
ULL n = (ULL)currentDiv;
if (ans.find(n) == ans.end() || currentVal < ans[n]) {
ans[n] = currentVal;
}
}
// 尝试添加下一个质因子
for (int e = 1; e <= lastExp; e++) {
// 计算新的 k 值
if (currentVal > INF / primes[idx]) break; // 乘法溢出检查
ULL newVal = currentVal;
for (int i = 0; i < e; i++) {
if (newVal > INF / primes[idx]) {
newVal = INF;
break;
}
newVal *= primes[idx];
}
if (newVal >= INF) break; // 超过上限
// 计算新的 n 值:currentDiv * C(sumExp + e, e)
// 使用 int128_t 计算组合数避免溢出
int128_t newDiv = currentDiv;
int s = sumExp;
for (int i = 1; i <= e; i++) {
newDiv = newDiv * (s + i) / i;
if (newDiv > INF) break; // 超过上限
}
if (newDiv > INF) continue;
// 递归搜索下一个质数
dfs(idx + 1, e, newVal, newDiv, sumExp + e);
}
}
int main() {
// 从第一个质数开始搜索
for (int e = 62; e >= 1; e--) {
ULL val = 1;
for (int i = 0; i < e; i++) {
if (val > INF / 2) {
val = INF;
break;
}
val *= 2;
}
if (val >= INF) continue; // 超过上限
// 对于单个质数的情况,currentDiv = 1
dfs(1, e, val, (int128_t)1, e);
}
// 处理输入输出
ULL n;
while (cin >> n) {
cout << n << " " << ans[n] << endl;
}
return 0;
}
代码说明
1. 关键函数 dfs
- 参数说明:
idx:当前质数索引lastExp:上一个质数的指数(保证非递增)currentVal:当前 k k k 值currentDiv:当前 n n n 值(用__int128存储)sumExp:当前总指数和
- 递归逻辑:
- 记录当前状态(如果合法)
- 枚举下一个质数的指数
- 计算新的 k k k 和 n n n
- 递归搜索
2. 溢出处理
- 使用
__int128处理 n n n 的中间计算 - 检查
currentVal > INF / primes[idx]防止乘法溢出 - 检查
newDiv > INF防止 n n n 超过上限
3. 预处理
- 从只有质数 2 2 2 的情况开始搜索
- 指数从大到小枚举( 62 62 62 到 1 1 1),因为较大指数对应较小 k k k
4. 存储与查询
- 使用
map<ULL, ULL>存储 n n n 到最小 k k k 的映射 - 查询时直接输出
ans[n]
算法正确性证明
1. 完备性
我们枚举了所有可能的指数序列 ( a 1 , a 2 , … , a m ) (a_1, a_2, \dots, a_m) (a1,a2,…,am),其中:
- a 1 ≥ a 2 ≥ ⋯ ≥ a m a_1 \ge a_2 \ge \dots \ge a_m a1≥a2≥⋯≥am
- 对应质数为
2
,
3
,
5
,
…
2, 3, 5, \dots
2,3,5,…
因此我们考虑了所有可能的 k k k 值。
2. 最小性
对于每个 n n n,我们记录的是所有对应 k k k 中的最小值。因为:
- 指数递减保证了质数从小到大分配
- 我们总是先尝试较大指数(对应较小质数),然后递归搜索
- 当找到相同的 n n n 时,如果当前 k k k 更小则更新
3. 边界处理
- k < 2 63 k < 2^{63} k<263:通过溢出检查保证
-
n
<
2
63
n < 2^{63}
n<263:通过
newDiv > INF检查保证 - 最多
1000
1000
1000 个测试用例:
map查询为 O ( log N ) O(\log N) O(logN),完全可接受
时间复杂度分析
搜索空间
- 最大指数: 62 62 62(因为 2 62 < 2 63 2^{62} < 2^{63} 262<263)
- 质数数量: 16 16 16 个足够
- 实际状态数:受限于 n < 2 63 n < 2^{63} n<263,状态数很少(几百个)
时间复杂度
- 预处理: O ( 状态数 ) O(\text{状态数}) O(状态数),约几百次递归
- 查询: O ( log N ) O(\log N) O(logN) 每次查询
- 总体:完全可接受
总结
本题的关键在于:
- 理解 f ( k ) f(k) f(k) 是多重集合的排列数
- 将问题转化为枚举指数序列
- 使用
DFS搜索所有可能的状态 - 注意溢出处理,使用
__int128 - 通过指数递减和质数顺序保证找到最小的 k k k
本题结合了数论、组合数学和搜索,是一道综合性较强的题目。掌握本题的解法对于理解质因数分解、排列组合和搜索优化都有很大帮助。
UVa 1575 因子排列问题解析


161

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



