HDOJ 1066 题解

来源:互联网 发布:徐老师淘宝店地址链接 编辑:程序博客网 时间:2024/06/06 05:16

HDOJ 1066  题解


Last non-zero Digit in N!


由于网络上的题解或模版诸多互相抄袭, 一知半解, 晦涩难懂. 难以有效的作为参考弄懂此题. 笔者作为一位ACM初学者水平能力有限, 但喜欢真正的理解与解决每道自己可以AC的题目, 所以结合自己2天的琢磨与分析总结了这篇题解. 

此题数目较大, n可能是有百位是无法直接计算n!的. 所以最先映入脑海的想法是从1开始进行乘数累加的分步阶乘计算., 每次只保留当前结果的最右非0值进行n次计算. 从初始的x=1, i=1开始计算x * i, 将结果的最右非0值赋值给x且i=i+1, 一直计算到i=n. 可是这个方法不但会在计算到乘以14(或者13, 笔者记不清了. )的时候因为进位出错导致之后的结果全为0之外, n次的循环计算也会导致程序超时不能AC. 正确的做法在于找出数字的规律, 是一道属于找规律的题. 弄懂此题比较复杂, 需要一些离散数学的知识. 

第一种情况  n<10  :直接枚举即可:int FirstTen[10]={1,1,2,6,4,2,2,4,2,8}. 

          第二种情况 n>=10 :需要详细的分析如下: 我们不难想到n!的尾部的0都来源于因子5与因子2(一对2与5产生一个0)。如果将这些因子去掉,则上述n次乘积的分步阶乘计算就会产生正确结果(虽然还是存在超时问题). 


定义1: G(n)为计算n!时将所有5的倍数均换成1(把可整除5的因子全忽略)后的各项乘积的总值. 


如: G(15)=1 * 2 * 3 * 4 *1 * 6 * 7 * 8 * 9 * 1 * 11 * 12 * 13 * 14 *1.

若我们忽略 <把n!中5的倍数都提取出来了> 这一先决条件, 那么n!的最右非0数就是G(n)值的最后一位, 即为 G(n)%10. 经过只求计算可以很容易的列举G(1)—G(20)的最右值(一定都是非0的):

            n:  0   1   2   3   4   5   6   7   8   9 

G(n)%10:  1   1   2   6   4   4   4   8   4   6

(因为前10个数的最终结果值是第一种情况可以直接计算出来,所以我们其实关心的是从10开始之后的情况)

   

           n:  10   11   12   13   14   15   16   17   18   19 

G(n)%10:  6     6     2     6     4    4     4     8     4     6


            n:  20   21   22   23   24   25   26   27   28   29 

G(n)%10:  6     6     2     6     4    4     4     8     4     6


好了,到这里已经露出一些端倪了。类似G(10)—G(19), G(20)—G(29)等之后的所有一组10个数的最右值都和上面给出的G(10)—G(19)一致。我们把这10个值作为基准表示为:int table[10] = {6, 6, 2, 6, 4, 4, 4, 8, 4, 6}. 我们通过找规律, 只需要1步计算就可以确定对于任意的n, G(n)的最右值: table[n%10] 


现在只需要把忽略的那些5的倍数全乘回来就是最终结果. 由于G(n)的最右非0值已经很容易求了, 就是G(n)的最后一位. 所以我们不想再因乘以因子5导致出现最后一位变成了0, 需要去考虑次低位是什么, 甚至次次低位是什么的复杂情况了.因为在取最右非0值的时候, 乘以1个因子5等同于除以1个因子2.  而且因子2的数目绝对足够匹配需要补乘上的因子5的数目. 因为无论是n!还是G(n), 其中都包含的因子2一定比忽略的因子5多(阶乘中2的倍数显然比5的倍数多). 比如10!中因子5只有5和10中各包含1个, 但是2, 4, 6, 8, 10中包含1+2+1+3+1个因子2. 


因为n!=(n/5)! * 5^(n/5) * G(n).例如15!=(n/5)! * 5^(n/5) * G(15)=3! * 5^3 * G(15). 所以求n!的最右非0值的问题可拆解为求(n/5)!的最右非0值的子问题 乘以 G(n)补乘(n/5)个5后的最右非0值 再取最右非0值的问题. 


定义2: F(n)为取n!的最右非0值, 即目标结果. 

定义3: C(n)为取 5^(n/5) *G(n) 的最右非0值, 也就是取G(n)除以 (n/5) 个因子2之后得到的最右值. 


所以有F(n) = ( F(n/5) * C(n) ) % 10. 


分析到这里思路已经很清晰了. 子问题用递归可以得到很简洁的解决. G(n)的最右非0值(也就是最右值)的已知给求取G(n)除以(n/5)个因子2后得到的C(n)提供了先决条件. 接下来我们来分析”除以”若干个2会发生什么变化. 这是一个特殊的除法. 


已G(10)%10=6为例: 

(1)”除以”1个2:  G(10)%10 / 2 = 8. 

在这里为什么结果为8而不是3呢,是因为虽然G(10)是第二种情况下求出的第一个结果,但G(10)已经包含乘数因子2,4,6,8,也就是包含了1+2+1+3 = 7个乘数因子2了。所以G(10) / 2 的结果一定是一个偶数(还有6个因子2),所以肯定是最后一位6向高位借位(变为16/2)得出结果8. (笔者认为这是一个很凑巧但正确的解释, 实际上这个特殊除法的规律是在n的值很小可直接求出n!的值时, 对比真正n!的最右非0值与G(n)的最右值得到的. 但是因为上述解释简明正确易懂, 即符合抽象规则又能给出很形象的解释, 笔者也只是在总结时偶然发现. ) 

(2)”除以”2个2:  (1) / 2 = 8 / 2 = 4. (如果向高位借位则为18 / 2 = 9显然不符)

(3)”除以”3个2:  (2) / 2 = 4 / 2 = 2. (同上)

(4)”除以”4个2:  (3) / 2 = 2 / 2 = 6. (向高位借位了) 

(5)”除以”5个2:  (4) / 2 = 6 / 2 = 8. (同(1)一致,出现循环了)


  综上所述, 这个规律被找到了, 便是G(n)%10”除以”(n/5)个2的这个特殊的除法结果4次一循环,如果G(n)%10 = 6, 则其循环为: 

—>6-/2-> 8 -/2-> 4 -/2->2 -/2->6. 

同理G(n)%10 = 2 or 4 or 8的情况均符合这个除以2的四次循环. 例如G(n)%10 = 8, “除以”7个2时,等同于除以 7%4 =3个2 ==> 8 -> 4 ->2, 所以结果为2. 

所以循环基准为:int Circle[4] = {2, 6, 8, 4}. (当然也可以是8, 4, 2, 6, 保持4个数字先后顺序即可. )


具体算法:  已知n, 先求G(n)的最右值:  table[n%10].

                 找到table[n%10]的值 在 Circle[4] 中的位置i. (i = 0,1,2,3)

                 C(n) = Circle[ (i + n/5 ) % 4].  F(n) = ( F(n/5) * C(n) % 10). 


AC代码如下:

[cpp] view plaincopy
  1. //  
  2. //  main.c  
  3. //  HDOJ1066  
  4. //  
  5. //  Created by Egger on 18/1/15.  
  6. //  Copyright (c) 2015年 Egger. All rights reserved.  
  7. //  
  8.   
  9. #include <stdio.h>  
  10. #include <string.h>  
  11. #define SIZE 10  
  12. #define MAXH 1000  
  13.   
  14. char Str[MAXH];  
  15. int Num[MAXH];  
  16.   
  17. const int FirstTen[SIZE] = {1,1,2,6,4,2,2,4,2,8};  
  18. const int Table[SIZE] = {6,6,2,6,4,4,4,8,4,6};  
  19. const int DivisionCircle[4] = {2,6,8,4};  
  20.   
  21.   
  22. int H(int X, int Y)  
  23. {  
  24.   
  25.     int i;  
  26.   
  27.     for(i = 0; i < 4; i++)  
  28.     {  
  29.         if (X == DivisionCircle[i])  
  30.         {  
  31.             break;  
  32.         }  
  33.     }  
  34.   
  35.     return DivisionCircle[(i+Y)%4];  
  36. }  
  37.   
  38. int F(char* s)  
  39. {  
  40.     int i,k;  
  41.     int Bit,Carry,Len,Result;  
  42.       
  43.     Len = (int)strlen(s);  
  44.     Result = 1;  
  45.       
  46.     if(Len < 2)  
  47.     {  
  48.         return FirstTen[s[0]-'0'];  
  49.     }  
  50.       
  51.     for(i = Len - 1; i >= 0; i--)  
  52.     {  
  53.         Num[Len-1-i] = s[i] - '0';  
  54.     }  
  55.     while(Len > 1)  
  56.     {  
  57.         k = Num[0];  
  58.         Carry = 0;  
  59.         Num[Len] = 0;  
  60.         for(i = 0; i <= Len; i++)  
  61.         {  
  62.             Bit = Num[i] * 2 +Carry;  
  63.             Num[i] = Bit % 10;  
  64.             Carry = Bit / 10;  
  65.         }  
  66.         for(i = 1; i <= Len; i++)  
  67.         {  
  68.             Num[i-1] = Num[i];  
  69.         }  
  70.         if(Num[Len] == 0)  
  71.             Len --;  
  72.           
  73.         Result = Result * H(Table[k], Num[0]+Num[1]*10) % 10;  
  74.     }  
  75.       
  76.     Result = Result * FirstTen[Num[0]] % 10;  
  77.       
  78.     return Result;  
  79. }  
  80.   
  81. int main(int argc, const char* argv[])  
  82. {  
  83.   
  84.     while(scanf("%s",Str) != EOF)  
  85.     {  
  86.         printf("%d\n",F(Str));  
  87.     }  
  88.     return 0;  
  89. }  
0 0
原创粉丝点击