UVa 1575 Factors

UVa 1575 因子排列问题解析

题目描述

算术基本定理指出,每个大于 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×55×220=2×2×52×5×25×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=p1a1p2a2pmam

其中 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 a1a2am(为了最小化 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=2a13a25a3(使用最小的质数来最小化 k k k

3. 关键观察

  1. 指数非递增:为了最小化 k k k,我们应让较大的指数对应较小的质数。
  2. 质数顺序:使用最小的质数 2 , 3 , 5 , 7 , … 2, 3, 5, 7, \dots 2,3,5,7,
  3. 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×
  4. 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 递归过程
  1. 基准情况:对于当前状态,如果 n n n 值合法(在 unsigned long long 范围内),更新答案。
  2. 扩展状态
    • 选择下一个质数 primes[idx]
    • 选择指数 e 1 ≤ e ≤ lastExp 1 \le e \le \texttt{lastExp} 1elastExp
    • 计算新的 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 优化技巧
  1. 指数递减:强制要求 a 1 ≥ a 2 ≥ … a_1 \ge a_2 \ge \dots a1a2,减少搜索空间。
  2. 提前剪枝
    • currentValnewVal 超过 2 63 2^{63} 263 时停止
    • currentDivnewDiv 超过 2 63 2^{63} 263 时停止
  3. 使用 __int128:防止中间计算溢出。
  4. 预处理质数:使用前 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:当前总指数和
  • 递归逻辑
    1. 记录当前状态(如果合法)
    2. 枚举下一个质数的指数
    3. 计算新的 k k k n n n
    4. 递归搜索

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 a1a2am
  • 对应质数为 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) 每次查询
  • 总体:完全可接受

总结

本题的关键在于:

  1. 理解 f ( k ) f(k) f(k) 是多重集合的排列数
  2. 将问题转化为枚举指数序列
  3. 使用 DFS 搜索所有可能的状态
  4. 注意溢出处理,使用 __int128
  5. 通过指数递减和质数顺序保证找到最小的 k k k

本题结合了数论、组合数学和搜索,是一道综合性较强的题目。掌握本题的解法对于理解质因数分解、排列组合和搜索优化都有很大帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值