今天领导录题的时候问了一个题,使我陷入了迷茫。原题目是这样的:给出n,求n的阶乘的结果的最后一位非零数字,和末尾0的个位数。例如 n = 12 n=12 n=12, 则 n ! = 1 × 2 × . . . × 12 = 479001600 n!=1\times 2\times ... \times 12=479001600 n!=1×2×...×12=479001600,则其最后一位非零数字为6,末尾0的个数为2。
思路1(正确)
这个思路是领导刚问我时我首先想到的,但是是一个比较笨而且复杂度较高的方法。时间复杂度 O ( n ( l o g n ) 2 ) O(n(logn)^2) O(n(logn)2)。
首先考虑到只有偶数乘5才回出现0,而大于2的偶数都是2的倍数,那我们只考虑 2 × 5 2\times 5 2×5就可以断定末尾0的个数。从而想到质因数分解,即1~n每个数都分解为质因数,同时把质因子2和5单独拿出来,其余质因子直接乘起来(模10乘即可,因为只需要个位数)。
提交到OJ验证该方法是正确的,耗时6毫秒。
#include <bits/stdc++.h>
using namespace std;
bool not_prime[1000005] = {false};
int main() {
int n;
cin >> n;
int count2 = 0, count5 = 0;//记一下2 5的个数, 5肯定比2少
int others_prod = 1;//记一下其他因子的积的个位数
for (int i = 2; i <= n; i++) {
if (not_prime[i])
continue;
for (int j = i; j <= n; j += i) {
not_prime[j] = true;
for (int temp = j; temp % i == 0; temp /= i) { //分解出质因子i
if (i == 2)
count2++;
else if (i == 5)
count5++;
else
others_prod = others_prod * i % 10;
}
}
}
for (int i = 0; i < count2 - count5; i++) //5肯定比2少,2*5匹配完后,剩余的2乘回去
others_prod = others_prod * 2 % 10;
cout << others_prod << " " << count5 << endl;
}
思路2(错误)
在我还没有完善思路1的代码时,领导给我发了一个代码,我一看如此简单(比我当时想到的思路1简单多了),并一度觉得是正确的,思考 迷茫 了一中午,才想明白。想记录下来,于是有了本文。菜的发慌。
该思路将问题的【最后一位非零数字】和【末尾0的个数】分别计算。其中【末尾0的个数】可以直接质因子分解求出因子5的个数,因为末尾的0只能来自偶数乘5,而质因子2的个数肯定比5多,所以只统计质因子5即可。至于【最后一位非零数字】,在中间计算过程中,舍去末尾的0(因为0可以不参与乘法的计算,填在结果的末尾即可),然后通过模除10舍去高位,因为最后结果只要求最后一位非零数字。 于是代码如下,耗时2毫秒。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, i, temp;
int ans1 = 1, ans5 = 0;
cin >> n;
for (i = 1; i <= n; i++) {
ans1 = ans1 * i;
while (ans1 % 10 == 0)
ans1 /= 10;
ans1 = ans1 % 10;
temp = i;
while (temp % 5 == 0) {
ans5++;
temp /= 5;
}
}
cout << ans1 << " " << ans5;
return 0;
}
我们举一个例子,按照上述代码运算,当 a n s 1 = 2 , i = 5 ans1=2,i=5 ans1=2,i=5时,最后一位非零数字为10,除10去0后是1。
对吗?
不对。
注意 a n s 1 ans1 ans1是上一轮乘法模除10得来的,虽然 a n s 1 = 2 ans1=2 ans1=2,但2只是个位数,其前面还有更多数字,如 a n s 1 ′ = 72 ans1'=72 ans1′=72,那么 72 × 5 = 360 72\times 5=360 72×5=360,最后一位非零数字不再是1,而是6。
再如 a n s 1 = 5 , i = 8 ans1=5,i=8 ans1=5,i=8,最后一位非零数字是4,但如果上一轮乘法模除10之前 a n s 1 = 25 ans1=25 ans1=25,那么 25 ∗ 8 = 200 25*8=200 25∗8=200,末尾非零数字是2。
什么原因呢?进位。在该代码中,是没有考虑到进位的,最简单的 2 × 5 2\times5 2×5要进一位, 125 ∗ 8 = 1000 125*8=1000 125∗8=1000要进三位,甚至还有进更多位的,所以该思路错误。那能不能针对原因改正改代码呢?见思路3。
思路3(正确)
验证了思路2的错误原因之后,有了最终的思路。时间复杂度
O
(
n
+
m
)
O(n+m)
O(n+m),其中m是
n
!
n!
n!进行质因子分解后2的个数。
通过上文我们知道了其实只考虑质因子2和5的个数就可以了,所以我们在进行阶乘的过程中,把质因子2和5单独分离出来,其余因子直接乘起来。最后,显然2会比5多,所以有多少5,
n
!
n!
n!结果末尾就会有多少个0,同时消耗掉对应数量的2,剩下的2再乘回总结果(product)即可。
耗时2毫秒
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
int count2 = 0, count5 = 0; //记一下2,5的个数
int product = 1; //记一下其他质因子的积的个位数
for (int i = 2; i <= n; i++) {
int temp = i;
for (; temp % 5 == 0; temp /= 5)
count5++;
for (; temp % 2 == 0; temp /= 2)
count2++;
product = product * temp % 10;
}
for (int i = 0; i < count2 - count5; i++) //5肯定比2少,2*5匹配完后,剩余的2乘回去
product = product * 2 % 10;
cout << product << " " << count5 << endl;
}