jzxx_1172 USACO 求阶乘

本文主要介绍了如何解决USACO中的求阶乘问题,详细讲解了输入输出格式,并给出了样例输入和输出,帮助理解算法实现。

题目描述

阶乘的定义如下: N! = 1 * 2 * 3 * ... * N-1 * N例如, 12! = 1*2*3*4*5*6*7*8*9*10*11*12 = 479001600最右的非0位是6,后面有2个0。写一个程序计算N!最右非0位和末尾的0的个数。

输入

一个整数N, 1 <= N <= 1,000,000

输出

一行输出2个整数:最右非0位,末尾0的个数。

样例输入

12

样例输出

6 2

    求N!的阶乘当N比较小的时候还是比较容易的,利用一个迭代就能够顺利的解决。而且在很多专业书中,介绍递归的时候经常会举这样的例子,顺带还能讲讲
迭代的价值。但是当N的值比较大的时候就比较麻烦了,int 和 long long 的范围都会爆掉。所以一般的人在求解 N!具体值得时候一般采用高精度,也就是利用
字符串或者整形数组来存储数据,其实字符串就是字符型数组。但是这个题目并没有要求我们求解 N! 的具体的值,而是求 N! 最尾不为零的数,这时候问题就简单
了。利用mod的分配律即  a*b (mod c) ≡ ((a mod c)*b) mod c 。
    于是我就利用这个性质编写了如下程序段:
      
          
        lastNum *= m;
        while(lastNum % 10 == 0)
            lastNum /= 10;     //处理lastNum 末尾零
        lastNum %= 10;
将 m 循环迭代相乘到 lastNum 中去,然后再处理掉 lastNum 末尾的零。最后取出 lastNum里面最末尾的数。
用这种编写方式,lastNum 声明为一个 int 类型就够了,而且数据也能处理到 5 亿。但问题出现了,当数据大于1000 的时候,结果值就总是 2 ,而且将过程中最后的
一部分数据打印出来,发现在某一个时候,数据出现了循环。反过来写即 从 1 开始往后面迭代,虽然在后面结果值没有循环,但是也变相的出现了循环即倒数第二个总是2,
然后乘以最后一个值后对 10 取模,获得最末尾非零的值。这样的结果显然是不对的。
没办法,我想到了用高精度来解决这个问题。

#include <stdio.h>
#define MAXN 10000
 
int main(){
    int n, m = 1;      // 1 <= n <= 1000000
    int zeroCnt = 0, lastNum = 1;
    int a[MAXN] = {0};
    a[0] = 1;
 
    scanf("%d", &n);
 
    while(m <= n){
        int t = m;
        while( t % 5 == 0 ){
            t /= 5;
            ++zeroCnt;
        }
        t = MAXN;
        while(a[--t]);   //a[t]为最后一个非零数
        int p = t;
        while(p >= 0)
            a[p--] *= m;
        for( p = 0; p <= t; ++p ){
            a[p+1] += a[p] / 10;
            a[p] %= 10;
        }                //处理进位
        ++m;
    }
    m = -1;
    while(a[++m] == 0);
 
    printf("%d %d", a[m], zeroCnt);
    //system("pause");
    return 0;
}

    

我的想法和这个描述有点差距。既然每次取一位数据会使数据陷入循环状态,这也许是因为在每一次取最后一位数据时候消除了其他位在相乘迭代的过程中最最末尾非零

数据的影响,那么我是不是可以在保留最后数据的时候多保留几位来减少对最后尾数非零数据的影响呢?可能也会产生影响,但在有限数据范围内,使这样的影响对答案透明

不就可以达到我们的要求了吗? 所以在取模的过程中,我定义了 一个 MAXN = 10000 , MAXN * MAXN = 100000000 < int 的范围,如果是 100000 的话则会大于 int 的

范围。故将 MAXN 定在了 10000 。 M*N <= 10000000000 < (int) ,满足题目要求。然后提交就 AC 了。 代码如下:

#include <stdio.h>
#define MAXN 10000

int main(){
    int n, m = 1;      // 1 <= n <= 1000000
    int zeroCnt = 0, lastNum = 1;

    scanf("%d", &n);

    while(n){
        int t = n;
        while( t % 5 == 0 ){
            t /= 5;
            ++zeroCnt;
        }

        lastNum *= n;
        while(lastNum % 10 == 0)
            lastNum /= 10;     //处理lastNum 末尾零
        lastNum %= MAXN;
        --n;
    }


    printf("%d %d", lastNum % 10, zeroCnt);
    //system("pause");
    return 0;
}

       时间性能上虽然接近O(N) 但比 lg(N) 差远了。 所以利用循环节来处理这个问题,要好太多了。



                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值