Description
给出一个数字N,求N的阶乘,其右边有多少个连续的零。 例如5!=120,其右边有1个零。
Format
Input
每行一个数字N,1 <= N <= 1000000000.
Output
每行一个数字。
Samples
输入数据 1
100
Copy
输出数据 1
24
Copy
Limitation
1s, 1024KiB for each test case.
一,朴素解法
最简单的方式就是按照题目意思,先把N的阶乘求出来,再来计算右边有多少个连续的0。基于此写出来的代码:
#include<bits/stdc++.h>
using namespace std;
long long n,sum = 1,k,d;
int main()
{
cin>>n;
for(int i = 1;i <= n;i++) sum *= i;
while(true)
{
k = sum % 10;
if(k == 0) d++;
else break;
sum /= 10;
}
cout<<d;
return 0;
}
通过计算一下N的阶乘的范围大小,发现当N=20时还在long long 的数据范围内,而当N=21时就已经超过数据范围了,所以用来解题是行不通的。
二,稍加优化
那就来看看在N的阶乘这个跟过程中到底是哪几个数字导致了末尾0的产生。
举例:N=5
1*2=2
1*2*3=6
1*2*3*4=24
1*2*3*4*5=120
当N乘到5时末尾出现了第一个0,而且10我们通常可以分解为2*5,因此我们推测末尾0的出现和2、5两个数字的数量有关,而同样我们可以的发现在1、2、3、4、5这几个数字当中,能提供出2的数字有2、4,一共可以提供出3个2,而能提供5的数字只有5,10=2*5这个式子中2的数量是供过于求的,所以我们着重看一下有哪些数字能提供5。
当N=100时,能提供5的数字有5,10,15,20,25……85,90,95,100;
并且25=5*5,可以提供两个5,于是按照思路,枚举从1到N的所有数字,看各个数字能提供出多少个5,就是末尾0的数量。
代码:
#include<bits/stdc++.h>
using namespace std;
long long n,k,d;
int main()
{
cin>>n;
for(int i = 1;i <= n;i++)
{
k = i;
while(k % 5 == 0)
{
d++;
k /= 5;
}
}
cout<<d;
return 0;
}
(这个代码居然过了)
同时,因为能提供5的数字只有5的倍数,可以修改代码为枚举所有5的倍数。
修改后代码为:
#include<bits/stdc++.h>
using namespace std;
int main()
{
long long n,k,d;
cin>>n;
for(int i = 5;i <= n;i += 5)
{
k = i;
while(k % 5 == 0)
{
d++;
k /= 5;
}
}
cout<<d;
return 0;
}
这个程序的时间复杂度为O(n / 5),表现还算是比较满意,但还有没有更好的做法呢?
三,最优解法
我们发现上面这个程序是将数字i一个个去做质因子分解,看是否能分出5这个质因子出来。
如果变换一个思路,我们能否成批成批的找出那些一定包含5这个质因子的数字呢?
我们将这些数字进行分类考虑
1:只能分解出来1个5出来的,则它们可统一写成5*x这样的形式
于是当N = 1000时
5 * x <= 1000
x <= 200
即找出来的数字形如5,10,15,20,25,30,35,40,45,50这样形式
2:能分解出来2个5出来的,则它们可统一写成25 * x这样的形式
于是25 * x <= 1000
x <= 40
即找出来的数字形如25,50,75,100这样形式,发现这些数字在前面已找出过一次了。因为一个数字能写成25 * x这样的形式,也必然可写成5 * x这样的形式。于是这些数字虽然能提供2个5出来,但在本次计算中只能累加一次。
3:能分解出来3个5出来的,则它们可统一写成125 * x这样的形式
125 * x <= 1000
x <= 8
4:能分解出来4个5出来的,则它们可统一写成625 * x这样的形式
625 * x <= 1000
x <= 1
5:能分解出来5个5出来的,则它们可统一写成3125 * x这样的形式
3125 * x <= 1000
x <= 0
于是当N=1000时,积累可提供200+40+8+1=249个5
最优解为:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n,k = 5,d = 0;
cin>>n;
if(n == 5)
{
cout<<1;
return 0;
}
while(k < n)
{
d += n / k;
k *= 5;
}
cout<<d;
return 0;
}
这个程序的时间复杂度为O(log 5 n),效果非常好,对于n <= 1e20都可以轻松求解。
通过上面几个程序步步深入,最终完美解决了这个问题,得到的启示如下
1:代码1只能按照试题的描述去暴力求解,结果效果很差。
2:代码2则通过分析得出0的产生,依赖于1到N之间的每个数字,其做质因子分解时能否提供质因子5出来。于是摒弃了大量根本无法产生质因子5的数字,但这个程序在计数的时候,仍是一个个进行统计
3:代码3在前面的基础上,进行了数学分析,得出了能提供质因子5的数字的一般形式,于是可以通过解方程,批量找出能产生质因子5的数字,并得到其产生的个数,而不是一个个去分解。整个程序从最核素的描述这个问题到通过分析,总结出问题的核心要素,揭示其一般性规律,最终达到高效解决问题。