Last non-zero Digit in N! HDU - 1066

题意:

求n!的最后一位非零数。

题目:

The expression N!, read as “N factorial,” denotes the product of the first N positive integers, where N is nonnegative. So, for example,
N N!
0 1
1 1
2 2
3 6
4 24
5 120
10 3628800

For this problem, you are to write a program that can compute the last non-zero digit of the factorial for N. For example, if your program is asked to compute the last nonzero digit of 5!, your program should produce “2” because 5! = 120, and 2 is the last nonzero digit of 120.

Input

Input to the program is a series of nonnegative integers, each on its own line with no other letters, digits or spaces. For each integer N, you should read the value and compute the last nonzero digit of N!.

Output

For each integer input, the program should print exactly one line of output containing the single last non-zero digit of N!.

Sample Input

1
2
26
125
3125
9999

Sample Output

1
2
4
8
2
8

分析:参考

我们发现n!末尾的0都是通过5和2想成得到的,为了把0去掉,我们把所有的因数2和5都提出来,放到最后再处理。

  1. N!中的N个相乘的数可以分成两堆:奇数和偶数。偶数相乘可以写成 ( 2 M ) ∗ ( M ! ) (2^M)*(M!) (2M)(M!) M = N / 2 M=N/2 M=N/2。M!可以递归处理,因此现在只需讨论奇数相乘。
  2. 考虑 1 ∗ 3 ∗ 5 ∗ 7 ∗ 9 ∗ 11 ∗ 13 ∗ 15 ∗ 17 ∗ . . . ∗ N 1*3*5*7*9*11*13*15*17*...*N 1357911131517...N(如果N为偶数则是N-1),这里面是5的倍数的有5,15,25,35,…,可以其中的5提出来,变成 ( 5 P ) ∗ ( 1 ∗ 3 ∗ 5 ∗ 7 ∗ 9 ∗ . . . ) (5^P)*(1*3*5*7*9* ...) (5P)(13579...),后面括号中共P项, P = ( N / 5 + 1 ) / 2 P=(N/5+1) /2 P=(N/5+1)/2,而后面的括号又可以继续提5出来,递归处理.现在剩下的数是 1 ∗ 3 ∗ 7 ∗ 9 ∗ 11 ∗ 13 ∗ 17 ∗ 19 ∗ . . . . 1*3*7*9*11*13*17*19*.... 137911131719....这些数我们只需要他们的个位数,因为 ( 1 ∗ 3 ∗ 9 ∗ 11 ∗ 13 ∗ . . . ) m o d (1*3*9*11*13* ...)mod (1391113...)mod 10 = ( 1 ∗ 3 ∗ 7 ∗ 9 ∗ 11 ∗ 13 ∗ . . . ) m o d 10=(1*3*7*9*11*13*...)mod 10=(13791113...)mod 10. 10. 10.我们列出余数表,1 3 1 9 9 7 9 1 1 3 1 9 9 7 9 ……。我们发现每八项 m o d mod mod 10的结果是一个循环.
  3. 算出奇数的结果后,我们再回头看统计了多少个2和5需要乘入。把2和5配对完都是N!后面的0,看剩下的2有几个。如果有剩下的2,考虑2^N的个位数又是2 4 8 6 2 4 8 6 ……每四项一个循环,找出这个个位数后,和前面的结果相乘,再取个位数就是答案。由于我们完全把2和5的因素另外处理,所以在所有的乘法中,都只需要计算个位数乘法,并且只保留个位数的结果。
  4. 由于是很大的数(前面三个点都可以不看Q—Q
    我们设F(N)表示N!的尾数。
    先考虑简单的。考虑某一个N!(N < 10),我们先将所有5的倍数提出来,用1代替原来5的倍数的位置。由于5的倍数全被提走了,所以这样就不会出现尾数0了。我们先把 0 − 9 0-9 09的阶乘的尾数列出来(注意,5的倍数的位置上是1),可以得到table[0—9]=(1, 1, 2, 6, 4, 4, 4, 8, 4, 6)。对于N < 5,直接输出table[N]即可;对于N>=5,由于提出了一个5,因此需要一个2与之配成10,即将尾数除以2。注意到除了0!和1!,阶乘的最后一个非零数字必为偶数,所以有一个很特别的除法规律: 2 / 2 = 6 , 4 / 2 = 2 , 6 / 2 = 8 , 8 / 2 = 4 2/2=6,4/2=2,6/2=8,8/2=4 2/2=6,4/2=2,6/2=8,8/2=4。比较特殊的就是 2 / 2 = 12 / 2 = 6 , 6 / 2 = 16 / 2 = 8 2/2=12/2=6,6/2=16/2=8 2/2=12/2=6,6/2=16/2=8.这样我们就可以得到如下式子: F ( N ) = { 2 N / 5 t a b l e [ N ] ( 0 < = N < 10 ) F(N)=\left \{ ^{table[N]}_{ 2^{N/5}} \right.(0 <= N < 10) F(N)={2N/5table[N](0<=N<10)
  • 再考虑复杂的。考虑某一个N!(N>=10),我们先将所有5的倍数提出来,用1代替原来5的倍数的位置。由于5的倍数全被提走了,所以这样就不会出现尾数0了。我们观察一下剩下的数的乘积的尾数,通过table表,我们发现这10个数的乘积的尾数是6,6*6的尾数还是6,因此我们将剩下的数每10个分成一组,则剩下的数的乘积的尾数只与最后一组的情况有关,即与N的最后一位数字有关。(因为根据最后一位就能知道它在尾数表中处于第几个位置,只要将尾数表之前的数相乘就得到最后一组的尾数。)由于我们把5的倍数提出来了,N!中一次可以提出[N/5]个5的倍数,有多少个5,就需要有多少个2与之配成10,所以有多少个5,最后就要除以多少个2。注意到除2的结果变化是4个一循环,因此如果有A个5,只需要除(A MOD 4)次2就可以了。A MOD 4只与A的最后两位数有关,很好求算。剩下的5的倍数,由于5已经全部处理掉了,就变成[N/5]!。于是,我们可以得到
    一个递归关系:
    F ( N ) = { 2 [ N / 5 ] m o d 4 F [ N ] ∗ t a b l e [ N 的 尾 数 ] ∗ 6 ( N > 10 ) F(N)=\left \{ ^{F[N]*table[N的尾数]*6}_{ 2^{[N/5] mod 4}} \right.(N >10) F(N)={2[N/5]mod4F[N]table[N]6(N>10)
    这样我们就得到了一个O(log5(N))的算法,整除5可以用高精度加法做,乘2再除10即可。整个算法相当巧妙。

AC代码:

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
const int M=1e3+10;
const int mp[20]= {1,1,2,6,4,2,2,4,2,8,4,4,8,4,6,8,8,6,8,2};
char s[M];
int main()
{
    while(~scanf("%s",s))
    {
        int n=strlen(s),ret=1,a[M];
        if(n==1)
            printf("%d\n",mp[s[0]-'0']);
        else
        {
            for(int i=0; i<n; i++)
                a[i]=s[n-1-i]-'0';
            while(n>1)
            {
                if(a[n-1]==0)
                    n--;
                ret=ret*mp[a[1]%2*10+a[0]]%5;
                int num=0;
                for(int i=n-1; i>=0; i--)//num=num/5
                {
                    num=num*10+a[i];
                    a[i]=num/5;
                    num%=5;
                }
            }
            printf("%d\n",ret+ret%2*5);
        }
    }
    return 0;
}

如何求n!中5的个数

int get5(int n)//计算n!中质因子5的出现次数
{
    if(n==0)
        return 0;
    return n/5+get5(n/5);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值