题解:洛谷 P1134 阶乘问题

【题目来源】

洛谷:P1134 [USACO3.2] 阶乘问题 - 洛谷

【题目描述】

也许你早就知道阶乘的含义,\(N\) 阶乘是由 \(1\)\(N\) 相乘而产生,如:

\[12!=1\times 2\times 3\times 4\times 5\times 6\times 7\times 8\times 9\times 10\times 11\times 12=479{,}001{,}600 \]

\(12\) 的阶乘最右边的非零位为 \(6\)

写一个程序,计算 \(N\ (1\le N\le5\times 10^7)\) 阶乘的最右边的非零位的值。

注意\(10{,}000{,}000!\) 的末尾有 \(2499999\) 个零。

【输入】

仅一行包含一个正整数 \(N\)

【输出】

一个整数,表示最右边的非零位的值。

【输入样例】

12

【输出样例】

6

【核心思想】

  1. 问题分析:求 \(N!\)\(N \le 5 \times 10^7\))的最右边非零数字,即阶乘结果末尾连续零去掉后的最后一个数字。直接计算阶乘会溢出且速度极慢,需要利用数学性质进行优化。

  2. 算法选择

    • 模拟乘法 + 末尾去零:在计算过程中,每次乘以 \(i\) 后,不断将末尾的零去掉(即除以 10),并取模一个大数(如 \(10^9\))保持数值可控。最后输出结果的个位数字。这种方法虽然简单,但对于 \(N\) 很大的情况仍可能较慢,需要进一步优化(但本题数据下可接受)。
    • 更优数学方法:统计因子 2 和 5 的数量,用因子 2 抵消因子 5 后,计算剩余数字的乘积模 10。但代码中使用的是直接模拟并去零取模的方法。
  3. 关键步骤(基于代码):

    • 初始化mul = 1,表示当前累积的阶乘值(去零后)。
    • 循环i 从 2 到 \(N\)
      • mul = mul * i
      • while (mul % 10 == 0) mul /= 10 去掉末尾所有的零。
      • mul %= 1000000000,将数值截断到 \(10^9\) 以内,避免溢出(因为只需保留非零末尾位)。
    • 输出mul % 10(个位数字)。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \times \text{去零次数})\),但去零次数有限,且 \(N\) 最大 \(5\times 10^7\) 时可能较慢(实际测试可能超时),需要更高效的数学方法。
    • 空间复杂度:\(O(1)\)
  5. 数学优化与末尾去零

    • 为什么去零:末尾的零来自因子 10(即 2×5),去掉这些零后,剩余数字的个位就是所求。
    • 取模的必要性:如果不取模,\(mul\) 会变得极大(超过 64 位),取模 \(10^9\) 可以保留足够多的低位信息,因为最终只需要个位,而 \(10^9\) 的倍数对个位无影响。
    • 性能问题:当 \(N\) 很大(如 \(5 \times 10^7\))时,\(O(N)\) 的循环可能仍较慢,更优的做法是统计因子 2 和 5 的数量,用因子 2 抵消因子 5 后,计算剩余乘积模 10 的循环节(每 10 个数一个周期),可将复杂度降至 \(O(\log N)\)
    • 适用场景:这种“求大阶乘非零末尾位”的问题,是数论中的经典问题,常用于考察数学简化和取模技巧。

【解题思路】

【算法标签】

普及 #数学

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n;
int main()
{
    cin >> n;  // 输入n
    long long mul = 1;  // 计算阶乘起始数
    for (int i=2; i<=n; i++) {  // 进行阶乘计算
        mul = mul * i;
        while (mul%10==0) mul /= 10;  // 如果末尾为0,就用辗转相除法去掉0
        mul %= 1000000000;  // 将mul控制在int范围内
    }
    cout << mul%10;  // 输出mul的个位数
    return 0;
}

【运行结果】

12
6
posted @ 2026-08-30 09:19  团爸讲算法  阅读(20)  评论(0)    收藏  举报