回 帖 发 新 帖 刷新版面

主题:[讨论]输入N,输出N!的最后一位非0的数字

此题唤起我童年的记忆...
当年我找规律....进行优化...最终没给出好结果。
我当时处理方式是利用C的位运算进行末几位的高精度计算,但是时间复杂度为O(N),当N很大时就没戏了。

昨天重新考虑了一下,得出一个可以证明的解法。时间复杂度为O(log(2, N)*log(5, N)),空间复杂度相同(递归)。

思路如下:
 从1到N中:所有奇数末尾的3、5、7、9个数后抛弃。所有偶数各分离掉一个2,就得到一个新的自然数列,1到N/2。
找到了一个基本思想。可以统计出所有数的因子中,2,5的个数,还有3、5、7结尾的因子的个数。下面进一步完善。

显然1到N中,每10个数就有3、5、7、9各一个。至于最后的不到10各的零头也很容易可以根据零头的大小推断是否还有3、5、7、9。这样就是把N除以10的商和余数分离出来,进行4个判断就可以得到准确的3、5、7、9的个数。
2的个数就更简单了,直接为N/2.。也就是说整个过程不用从1到N依次分析,而是只要分析N这个数。
然后就考虑另一个情况:末尾是5的数只被计为一个5就被丢弃是不正确的,应该将其中的5分离出来然后再处理它的末位。于是我们每次做完以上操作后要进行另外一个递归,把N个数中5的倍数之外的数丢弃,然后各提取一个因子5,得到一个新序列1到N/5。然后统计其中的5、3、7、9的个数。方法同上,还是只分析N。

这样把2、5和3、7、9结尾因数的个数统计出来,5的个数抵消掉一部分2,最后就相当于若干2、3、7、9的相乘。我们找到的规律是:

个数  1   2   3   4   5
  2   2   4   8   6   2
  3   3   9   7   1   3
  7   7   9   3   1   7
  9   9   1   9   1   9

就是说这些它们幂的末尾数字都以4为循环周期,于是我们没必要用精度很高的空间去记录它们准确的个数,只要记录个数除以4的余数。

以上过程很难描述清楚,我还是建议大家结合自己的思考。

我自己的程序是C++的,有兴趣的可以参考一下,嫌麻烦没用高精度,谁有空改改或者译成Basic,谢了。因为该问题是在此版见到的,因此还是贴在这里讨论。
在此感谢“狱中肺炎”同学的四行秒杀1000!程序帮我验证结果。源程序位置:http://blog.programfan.com/article.asp?id=25193

回复列表 (共26个回复)

21 楼

后面那个程序没看懂,只看出那个4768……是5^21,正在研究……
[SP1]N=1时有问题,可能需要单独处理一下……
[SP2]我在写自己程序的时候就联想到,那个递归实际上隐含着2进制、5进制转换的原理,觉得即使写成循环还是有优化的余地。Moz的那个“不知所云”的程序似乎就是用5进制转换,然后按位处理写出来的。

22 楼

这题我也研究了半天.
我的想法是:找出两个一位数的乘积个位为1的所有算式:
1*1 3*7 9*9
根据这3道算式,就可以更方便的得出结果了.
(1)把1-n之间所有的数按以下方法处理:
   1、奇数:
    如果这个奇数的个位是1就舍弃,否则记下它的个位(3579)
   2、偶数:
    把这个偶数分解2,直到分解完2为止,剩下的数一定是个奇数,按奇数的方法处理。
(2)取出上面记下的2、3、5、7、9的个数
   1、把2和5对掉。
   2、把3和7对掉。
   3、看9的个数,如果是奇数,保留一个9,否则保留1。
(3)把对掉之后的数字相乘,输出个位。
以n=20为例:
1*2*3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*19*20
经过(1)处理后:
2*3*(2*2)*5*(2*3)*7*(2*2*2)*9*(2*5)*(2*2*3)*3*(2*7)*5*(2*2*2*2)*7*(2*3*3)*9*(2*2*5)
=2^18*3^6*5^4*7^3*9^2
经过(2)处理后:
2^14*3^3*1*1*1
=2^14*3^3
经规律可知,2^14的个位为2,3^3的个位为7,所以{20}=4.

23 楼

楼上的年纪最小,嘿嘿~~~来,给颗糖,你研究研究Moz的程序,我睡会儿觉~~~~~~

24 楼

1. 把3与7对掉是正确的

2. N=1时得到的结果应该是6,这是正确的
   N=0时得到的也应该是6,这是一个数列,是对的
   虽然实际运算的结果并非如此,但数列的排列原来的确就是这个样子的.
   你研究一下我第一次做出来的那个程序的代码原理就可以得知,
   其实每5个数的序列都是以下4种情形:之前的程序是在这个基础上出来的.

       0,1,2,3,4,5,6,7,8,9,10
1      6 6 2 6 4
2      2 2 4 2 8
3      8 8 6 8 2
4      4 4 8 4 6

从2,3,4的结果可以递推得 f(0)=6, f(1)=6

25 楼

数列是6开头没错,可是N=1时还是应该判断一下给个1出来...
1的问题在于,它是2的0次幂,并不符合周期。我的程序中也有类似的特殊的判断,这也是我一开始给indices数组赋值-1的原因。程序编出来后有时不止要用大数据测试,还要用一些特殊的数据,比如0、1,进行测试的。

26 楼

看懂了,这里说得比你博客里详细~

我来回复

您尚未登录,请登录后再回复。点此登录或注册