1 条题解

  • 0
    @ 2025-12-8 22:43:46

    你好!我是你的OI教练。很高兴看到你挑战 GESP 五级的题目。五级题目通常开始涉及更深入的数论算法优化知识。

    这道题《因数分解》是数论中的经典入门题——质因数分解(Prime Factorization)。虽然题目逻辑不难,但在 101210^{12} 的数据规模下,它考验的是你对时间复杂度的敏感度和数据类型的把控。

    我们还是按老规矩,先拿出草稿纸,把解题思路拆解开来。


    1. 读题关键词:你的“雷达”响了吗?

    1. N1012N \le 10^{12}
      • 红色警报int 的最大值约为 2×1092 \times 10^9。这个 NN 远超 int 范围。
      • 动作:必须使用 long long 存储 NN
    2. “质因数由小到大排列”
      • 暗示我们可以从 2 开始,尝试去除 NN。因为从小到大尝试,能整除的一定是质数(因为如果是合数,它的因子早在之前就被除干净了)。
    3. 格式要求
      • * 左右有空格,^ 左右没空格。
      • 这是经典的**“栅栏问题”**:第一个元素前不能打印 *,从第二个元素开始,在打印前先打印 *

    2. 预备知识点

    • 试除法(Trial Division)
      • 要分解 NN,不需要遍历到 NN,只需要遍历到 N\sqrt{N} 即可。
      • 原理:如果 NN 有因数 aabb 使得 a×b=Na \times b = N,那么 aabb 中至少有一个小于等于 N\sqrt{N}
      • 效率1012=106\sqrt{10^{12}} = 10^6。循环 100 万次,计算机在 10ms 内就能完成,绝对不会超时。
    • 取模与除法
      • while (n % i == 0):如果能整除,就一直除,统计次数(指数)。

    3. 启发式教学:草稿纸上的推演

    假设我们要分解 N=20N = 20

    步骤 当前 N 尝试因子 i 能整除吗? 操作 累计输出
    1 20 2 cnt++, 20/2=1020/2=10
    2 10 cnt++, 10/2=510/2=5
    3 5 停止除2。cnt=2 2^2
    4 3 跳过
    5 4 (其实不会遇到4) (因为4的因子2已被除尽)
    ...
    Loop结束 5 original4\sqrt{original} \approx 4 循环终止 此时 N=5>1N=5 > 1
    收尾 - 输出剩余的N 2^2 * 5

    关键逻辑: 循环条件是 i * i <= n。 循环结束后,如果 N>1N > 1,说明剩下的这个 NN 就是最大的那个质因数(比如分解 22,循环到 224\sqrt{22} \approx 4 结束,剩下 11,直接输出)。


    4. 标准代码 (NOIP C++14)

    /**
     * 题目:B3871 [GESP202309 五级] 因数分解
     * 考点:质因数分解、试除法、格式化输出
     * 难度:普及- / GESP五级
     */
    
    #include <iostream>
    #include <cmath>
    
    using namespace std;
    
    void solve() {
        // 关键点1:数据范围 10^12,必须用 long long
        long long n;
        cin >> n;
    
        // 这是一个标记,用来处理由 " * " 分隔的格式问题
        // true 表示当前是输出的第一个项,不需要在前面加乘号
        bool first = true;
    
        // 关键点2:试除法
        // 只需要循环到 sqrt(n) 即可。注意 i*i <= n 可能会溢出,建议写成 i <= n/i 或者用 sqrt
        // 但因为 i 是 long long 且 n 最大 10^12,i*i 最大 10^12,不会溢出 long long
        for (long long i = 2; i * i <= n; i++) {
            // 如果 i 是 n 的因子
            if (n % i == 0) {
                int cnt = 0; // 统计指数
                
                // 将 i 除干净
                while (n % i == 0) {
                    cnt++;
                    n /= i;
                }
    
                // 输出格式控制
                if (!first) {
                    cout << " * ";
                }
                cout << i;
                if (cnt > 1) {
                    cout << "^" << cnt;
                }
    
                // 既然已经输出了第一项,后面就都不是第一项了
                first = false;
            }
        }
    
        // 关键点3:收尾
        // 如果循环结束后 n 仍然 > 1,说明剩下的 n 是一个大于 sqrt(原始n) 的质数
        if (n > 1) {
            if (!first) {
                cout << " * ";
            }
            cout << n;
            // 注意:此时 n 的指数一定是 1,所以不需要输出 ^
        }
        
        cout << endl;
    }
    
    int main() {
        // IO 优化
        ios::sync_with_stdio(false);
        cin.tie(0);
    
        solve();
    
        return 0;
    }
    

    5. 数据生成器 (1.in - 10.in)

    为了生成高质量的 101210^{12} 级别数据,我们需要组合大质数、完全平方数等边界情况。

    /**
     * B3871 数据生成器
     * 生成 1.in/1.out ~ 10.in/10.out
     */
    
    #include <iostream>
    #include <fstream>
    #include <string>
    #include <vector>
    #include <cstdlib>
    #include <ctime>
    #include <algorithm>
    #include <cmath>
    
    using namespace std;
    
    // ==========================================
    // Part 1: 标准题解逻辑 (用于生成 .out)
    // ==========================================
    string solve(long long n) {
        string ans = "";
        bool first = true;
        for (long long i = 2; i * i <= n; i++) {
            if (n % i == 0) {
                int cnt = 0;
                while (n % i == 0) {
                    cnt++;
                    n /= i;
                }
                if (!first) ans += " * ";
                ans += to_string(i);
                if (cnt > 1) ans += "^" + to_string(cnt);
                first = false;
            }
        }
        if (n > 1) {
            if (!first) ans += " * ";
            ans += to_string(n);
        }
        return ans;
    }
    
    // ==========================================
    // Part 2: 数据构造工具
    // ==========================================
    
    // 生成大的随机数
    long long randLL() {
        // rand() 通常只有 15位 (32767),需要拼接
        long long r = (long long)rand() << 48 | (long long)rand() << 32 | (long long)rand() << 16 | rand();
        return abs(r); // 确保正数
    }
    
    // 生成范围内的随机数 [min, max]
    long long randRange(long long min, long long max) {
        return min + randLL() % (max - min + 1);
    }
    
    // 简单的质数判断,用于构造数据
    bool isPrime(long long n) {
        if (n < 2) return false;
        for (long long i = 2; i * i <= n; i++) {
            if (n % i == 0) return false;
        }
        return true;
    }
    
    // 获取一个大的质数
    long long getPrime(long long min, long long max) {
        while (true) {
            long long p = randRange(min, max);
            if (isPrime(p)) return p;
        }
    }
    
    // ==========================================
    // Part 3: 测试点生成
    // ==========================================
    void makeData(int fileId) {
        string inName = to_string(fileId) + ".in";
        string outName = to_string(fileId) + ".out";
        ofstream fin(inName);
        ofstream fout(outName);
    
        long long N;
    
        if (fileId <= 2) {
            // 1-2: 小数据,基础测试
            N = randRange(2, 100);
        } else if (fileId == 3) {
            // 3: 纯大质数 (测试循环是否正确跑到 sqrt)
            N = getPrime(1e9, 1e11);
        } else if (fileId == 4) {
            // 4: 2的幂次 (测试 ^ 输出)
            // 2^39 < 10^12
            N = 1;
            int p = randRange(30, 39);
            for(int i=0; i<p; i++) N *= 2;
        } else if (fileId == 5) {
            // 5: 完全平方数 (测试 p^2)
            long long root = getPrime(1000, 10000);
            N = root * root;
        } else if (fileId == 6) {
            // 6: 两个大质数的乘积 (测试收尾 n > 1)
            long long p1 = getPrime(100000, 200000);
            long long p2 = getPrime(100000, 200000);
            N = p1 * p2;
        } else if (fileId <= 8) {
            // 7-8: 随机大合数
            N = randRange(1e10, 1e12);
        } else if (fileId == 9) {
            // 9: 边界测试,非常接近 10^12 的大质数
            N = getPrime(1e12 - 10000, 1e12);
        } else {
            // 10: 题目最大值 (如果是合数或质数都行,这里直接取10^12)
            N = 1000000000000LL;
        }
    
        fin << N << endl;
        fout << solve(N) << endl;
    
        fin.close();
        fout.close();
        cout << "Generated Case " << fileId << ": N=" << N << endl;
    }
    
    int main() {
        srand((unsigned)time(NULL));
        for (int i = 1; i <= 10; i++) {
            makeData(i);
        }
        return 0;
    }
    

    教练总结

    这道题是 GESP 五级的分水岭,标志着从“模拟”走向“算法”。

    1. 时间复杂度意识:如果不理解试除法的 N\sqrt{N} 原理,直接写 for (i=2; i<n; i++),在 N=1012N=10^{12} 时程序会跑几千年(超时)。
    2. 数据类型敏感度:时刻盯着 10910^9 这个界限。超过它,就要用 long long
    3. 细节:处理好末尾的那个质因数(循环结束后的 if (n > 1)),是这道题最容易丢分的地方。
    • 1

    信息

    ID
    14213
    时间
    1000ms
    内存
    32MiB
    难度
    3
    标签
    递交数
    1
    已通过
    1
    上传者