Acwing 1381.阶乘 题解(三种解法)
题目传送门
解题思路:
解法一:高精度乘法
本题要我们求一个数阶乘后末尾第一个不为零的数。本题\(N\)的数据范围对于阶乘来说是很大的,因此直接用C++自带的数据类型来计算肯定是不行的。当然,我们可以直接实现一个高精度的乘法,将阶乘结果直接算出来也是可以的。
代码
#include<bits/stdc++.h>
using namespace std;
vector<int> mul(vector<int> &a,int b)
{
int n=a.size();
vector<int> c;
int t=0;
for(int i=0;i<n;i++)
{
t=a[i]*b+t;
c.push_back(t%10);
t/=10;
}
while(t)
{
c.push_back(t%10);
t/=10;
}
while(c.size()>0&&c.back()==0) c.pop_back();
return c;
}
int main()
{
int n;
cin>>n;
vector<int> a;
a.push_back(1);
for(int i=1;i<=n;i++)
{
a=mul(a,i);
}
for(int i=0;i<a.size();i++)
{
if(a[i]!=0)
{
cout<<a[i];
break;
}
}
return 0;
}
解法二:取模运算
我们也可以换一种思路来看,如果我们不想实现高精度乘法的话,我们应该怎么得到末尾第一个不是零的数,有一个很自然的想法就是我们只记录每次乘法运算完后的末尾非零数值,但是这样的做法是有问题的。比如\(24!\)的末尾非零数值为\(6\),那么按照我们的算法,计算\(25!\)得到的末尾数字是\(5\),然而实际上\(25!\)的末尾非零数值是\(4\)。之所以会出现这样的情况,是因为数值相乘有进位的时候,我们末尾是可能变成\(0\)的,因此我们新的末尾数值会是先前某位数值相乘后累加的结果,我们目前的策略是不能够正确计算这样的情况。
那么我们该如何解决这样的问题呢?我们可以利用模运算多保留几位在非零数值前的数字,这样就可以将我们漏掉的情况加入计算了。现在我们需要考虑如何需要保留几位,这里要保证产生最多零的情况下前一个非零要精确,那么\(1000\)以内相邻两数能产生最多零的个数为\(3\),所以我们这里模取\(1000\)即可,当然取大一些也是可以的。
代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int res=1;
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
res=res*i;
while(res%10==0)
{
res/=10;
}
res=res%1000;
}
cout<<res%10;
return 0;
}
解法三:数学
我们来看一下题目结果的数学表达式
\[\frac{n!}{10^k}mod\ 10
\]
通过上面的公式我们可以看出,我们其实是消除了\(n!\)中对末尾\(0\)的所有贡献,现在我们对一个数进行质因数分解,我们会发现,一个数的末尾\(0\)的个数,是由质因数分解后\(2\)与\(5\)相乘所贡献的。所以只要先对\(1\sim n\)中的每个数中取出\(2\)与\(5\),将剩下的数相乘取末尾数字,最后把多出来的\(2\)或者\(5\)带回相乘即可。
代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
long long res=1;
int n;
cin>>n;
int fac_2=0,fac_5=0;
for(int i=2;i<=n;i++)
{
int t=i;
while(t>0&&t%2==0) fac_2++,t/=2;
while(t>0&&t%5==0) fac_5++,t/=5;
res=res*t%10;
}
//由于2的个数定比5多
for(int i=0;i<fac_2-fac_5;i++)
{
res=res*2%10;
}
cout<<res;
return 0;
}

浙公网安备 33010602011771号