题解:洛谷 P1134 阶乘问题
【题目来源】
【题目描述】
也许你早就知道阶乘的含义,\(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
【核心思想】
-
问题分析:求 \(N!\)(\(N \le 5 \times 10^7\))的最右边非零数字,即阶乘结果末尾连续零去掉后的最后一个数字。直接计算阶乘会溢出且速度极慢,需要利用数学性质进行优化。
-
算法选择:
- 模拟乘法 + 末尾去零:在计算过程中,每次乘以 \(i\) 后,不断将末尾的零去掉(即除以 10),并取模一个大数(如 \(10^9\))保持数值可控。最后输出结果的个位数字。这种方法虽然简单,但对于 \(N\) 很大的情况仍可能较慢,需要进一步优化(但本题数据下可接受)。
- 更优数学方法:统计因子 2 和 5 的数量,用因子 2 抵消因子 5 后,计算剩余数字的乘积模 10。但代码中使用的是直接模拟并去零取模的方法。
-
关键步骤(基于代码):
- 初始化:
mul = 1,表示当前累积的阶乘值(去零后)。 - 循环:
i从 2 到 \(N\):mul = mul * i。- 用
while (mul % 10 == 0) mul /= 10去掉末尾所有的零。 mul %= 1000000000,将数值截断到 \(10^9\) 以内,避免溢出(因为只需保留非零末尾位)。
- 输出:
mul % 10(个位数字)。
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(N \times \text{去零次数})\),但去零次数有限,且 \(N\) 最大 \(5\times 10^7\) 时可能较慢(实际测试可能超时),需要更高效的数学方法。
- 空间复杂度:\(O(1)\)。
-
数学优化与末尾去零:
- 为什么去零:末尾的零来自因子 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
浙公网安备 33010602011771号