求N!后面有多少个连续的零

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的出现和25两个数字的数量有关,而同样我们可以的发现在12345这几个数字当中,能提供出2的数字有24,一共可以提供出32,而能提供5的数字只有510=2*5这个式子中2的数量是供过于求的,所以我们着重看一下有哪些数字能提供5

N=100时,能提供5的数字有510152025……859095100

并且25=5*5,可以提供两个5,于是按照思路,枚举从1N的所有数字,看各个数字能提供出多少个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:只能分解出来15出来的,则它们可统一写成5*x这样的形式

于是当N = 1000

5 * x <= 1000

x <= 200

即找出来的数字形如5,10,15,20,25,30,35,40,45,50这样形式

2:能分解出来25出来的,则它们可统一写成25 * x这样的形式

于是25 * x <= 1000

x <= 40

即找出来的数字形如25,50,75,100这样形式,发现这些数字在前面已找出过一次了。因为一个数字能写成25 * x这样的形式,也必然可写成5 * x这样的形式。于是这些数字虽然能提供25出来,但在本次计算中只能累加一次。

3:能分解出来35出来的,则它们可统一写成125 * x这样的形式

125 * x <= 1000

x <= 8

4:能分解出来45出来的,则它们可统一写成625 * x这样的形式

625 * x <= 1000

x <= 1

5:能分解出来55出来的,则它们可统一写成3125 * x这样的形式

3125 * x <= 1000

x <= 0

于是当N=1000时,积累可提供200+40+8+1=2495

最优解为:

#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的产生,依赖于1N之间的每个数字,其做质因子分解时能否提供质因子5出来。于是摒弃了大量根本无法产生质因子5的数字,但这个程序在计数的时候,仍是一个个进行统计

3:代码3在前面的基础上,进行了数学分析,得出了能提供质因子5的数字的一般形式,于是可以通过解方程,批量找出能产生质因子5的数字,并得到其产生的个数,而不是一个个去分解。整个程序从最核素的描述这个问题到通过分析,总结出问题的核心要素,揭示其一般性规律,最终达到高效解决问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值