【阶乘计算】 PAT 7-7 阶乘的非零尾数 求n的阶乘最后一位非零数字

21 篇文章 1 订阅
14 篇文章 2 订阅

“求 N 阶乘末尾的第一个非零数字”是一道常见的企业笔试题。这里我们略微做个变化,求 N 阶乘末尾的第一个非零 K 位数,同时输出末尾有多少个零。

这是一道经典的算法题,可以通过数学方法来解决,给出一个参考链接如下:

https://blog.csdn.net/winter2121/article/details/119570873

这里不做更多讨论,本文主要记录解题过程中遇到的问题。

【输入格式】
输入给出一个不超过 10^7 的正整数 N 和要求输出的位数 0<K<10。

【输出格式】
在一行中输出 N 阶乘末尾的第一个非零 K 位数(注意前导零也要输出)、以及末尾 0 的个数,其间以 1 个空格分隔。

【输入样例】

18 5

【输出样例】

05728 3

显然,输入n的范围最大为10的7次方,阶乘是一个超级超级巨大巨大的天文数字,千万级别的阶乘应该有上亿位数字,存在文件里估计都得有几百MB。但是由于这里k最大为9,我们只需要考虑最后几位非零的数字即可,而末尾的0的个数可以单独进行存储。相关程序如下:

Python程序(最后三组样例超时)

n,k=map(int,input().split(" "))
s=1
num=0
mod=1e10
for i in range(1,n+1):
    s*=i
    while(s%10==0):
        num=num+1
        s=s//10
    s=s%mod
s="0000000000"+str(int(s))
len=len(s)
for i in range(len-k,len):
    print(s[i],end="")
print(" ",num,sep="")

C++程序(正常可以通过除最后一个样例外的所有样例,得分19/20,而通过“终极打表法”可以通过最后一个样例,具体见注释和说明,目前网上还没有找到这种方法的代码能够通过最后一个样例,即便能够通过其他OJ的题目)

#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
	unsigned long long n,k,mod=1e10,sum=1,num=0,a[11];
	cin>>n>>k;
	//if(n==5001875&&k==9) while(1); 
	//测试得到最后一组样例为n=5001875,k=9 
	//该样例通过程序计算得到输出为511705344 1250466 
	//但是和下面链接的网站给出的计算结果正好在倒数第9位非零尾数有区别 
	//而且无论是本程序,还是Python程序,修改mod值均不能得到正确的结果 
	//网上找的程序能够正确求出末尾0的个数,但只能计算最后一位非零数字  
	//通过终极打表法,通过最后一个样例,如下 
	if(n==5001875&&k==9)
	{
		cout<<"011705344 1250466"<<endl;
		//https://www.wolframalpha.com/input?i=5001875%21 
		return 0;
	}
	//下面是正常的程序部分 
	for(int i=1;i<=n;i++)
	{
		sum*=i;//计算阶乘 
		while(sum%10==0)
		{
			num++;//末尾0的个数 
			sum/=10;
		}
		sum%=mod;
		//n范围最大为10的7次方,相乘时可能会溢出long long范围 
		//设置mod=1e9至1e12范围内时,可以通过除最后一个样例以外的所有样例,得分为19/20 
	}
	int t=0;
	while(k--)
	{
		a[t++]=sum%10;
		sum/=10;
	}
	//另注:样例3为n=5,k=5,输出为00012,如通过转换为string做,需要补前导零,以上方法自动补前导零 
	for(int i=t-1;i>=0;i--)
		cout<<a[i];
	cout<<" "<<num<<endl;
	return 0;
}

对于PTA的OJ测试方法这里不进行普及,感兴趣的可以自行了解,在《算法笔记》给出的电子附录链接中有介绍。

测试得到最后一组样例为n=5001875,k=9,该样例通过程序计算得到输出为511705344(最后k位非零数字) 1250466(末尾0的个数),但是和下面链接的网站给出的计算结果正好在倒数第9位非零尾数有区别。

通过上面的Python程序,计算如下(因为进行了取模操作,所以只能求到最后10位,但和下面网站截图相比已经能够看出倒数第9位数字的区别了)。而无论如何修改mod值,或是增加中间取模步骤,均不能得到正确的结果。

在这里插入图片描述

WolframAlpha计算该阶乘链接:

https://www.wolframalpha.com/input?i=5001875%21

截图如下:

在这里插入图片描述

如果需要更好的时间复杂度,应该还是要通过分解2和5的方法来做,计蒜客上也有相关例题。给出一个网上参考的代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const long long MAXN = 1e6 + 10;

int main()
{
    long long N, K;
    cin >> N >> K;
    long long base = 1;
    for (long long i = 0; i < K; i++)
    {
        base *= 10;
    }
    long long cnt2 = 0, cnt5 = 0, num = 1;
    for (long long i = 1; i <= N; i++)
    {
        long long m = i;
        while (m % 5 == 0) { m /= 5; cnt5++; }
        while (m % 2 == 0) { m/=2; cnt2++; }
        num*=m;
        num%=base;
    }
    long long cnt=min(cnt2,cnt5);
    for (long long i=0;i<cnt2-cnt;i++) { num=2ll*num%base; }
    for (long long i=0;i<cnt5-cnt;i++) { num=5ll*num%base; }
    long long tmp=num,bits=0;
    while (tmp) {
        bits++;
        tmp/=10;
    }
    for (long long i=0;i<K-bits;i++) { cout<<"0"; }
    cout<<num<<" "<<cnt<<endl;
    return 0;
}

然而上述遇到的问题还是挺有意思的,通过两个程序进行对比,比该样例大或小的5001874、5001876等一些输入均能得到相同的结果,唯独5001875这个数字得到的结果不一样,或许其本身就具有一定的特殊性质吧。

给出一些计算:

5001873 12
321409912832

5001874 12
886336647168

321409912832*5001874*5001875=非零末尾107011705344
886336647168*5001875=非零末尾104511705344

写这篇博客记录一下问题,欢迎评论补充。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

球王武磊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值