给定一个数N(N <= 10 ^ 1000),如何快速求得N!的最末位非零数是一个经典的问题。一直以来都被这个问题困扰,今天仔细想了下,终于给想通了,尽管可能有些笨拙,现把想法记录于此。
在N很小的情况下,有一个简便的方法:求出1到N之间每个数的2的因子数和5的因子数,记为F(2)和F(5),显然F(2) >= F(5)。由于在末尾只有2和5相乘才能产生0,如果我们把2和5抛去,那么肯定不会有0,这样就可以一边乘一边模10,防止溢出。剩下的一堆2和5如何处理呢?因为2肯定比5多,因此最末位肯定是偶数(0的阶乘和1的阶乘除外)。而一个偶数不停地乘2,最末位的规律是:2 -> 4 -> 8 -> 6 -> 2 -> ...出现了4位1循环,这样我们先用F(2) - F(5),使得一部分2和5匹配上,2 * 5 = 10,对末尾不产生影响,剩下的2就模一下4,剩几再乘几次2就可以了。
但是这个方法在N非常大的时候肯定就不行了,但是可以利用找循环这个思想继续做。如果算阶乘的时候跳过5的倍数,记G(n)为跳过5的倍数的时候,从1乘到n的最末非零位,也就是把5的倍数当1乘。可以发现:
G(1) = 1, G(2) = 2, G(3) = 6, G(4) = 4, G(5) = 4, G(6) = 4, G(7) = 8, G(8) = 4, G(9) = 6, G(10) = 6, G(11) = 6, G(12) = 2, G(13) = 6...
又出现了循环,每10个数循环一次。如何计算G(n)就变的很简单,求出n的最末位,就知道对应的G(n)是多少了,当然需要特判n = 1的情况。由于我们把5的倍数的数都提出来了,提出来的这些数(5、10、15、20、25、30...)每个除以5后又组成了一个阶乘序列!除完5一共提出了n / 5个5,根据之前的分析,每个5都可以拿出一个2和它配对然后把它消去,这样一个5就相当于少一个2,我们就要把原来的数乘以3个2(模四循环)。这样一来5的个数其实也可以模四,模完四之后剩k的话,就可以乘以k个8,就把所有的5消去了。现在总结一下:对一个数n的阶乘,计算它的末尾非零位,先计算G(n),相当于非5的倍数的数的乘积最末非零位先算好了,然后乘以n / 5 % 4个8,处理了提出的n / 5个5,这样之后还剩下n / 5的阶乘没有算。递归的求解n / 5的阶乘的最末位非零数,再乘上去就得到结果了。
这个做法的复杂度就很低了,达到O(log n),对于10 ^ 1000的数据,利用高精度做就行了。利用这种循环的思想,算排列数P(n, k)的最末非零数也就可以做到了。
附HOJ 1013代码:
1#include <cstdio>
2#include <cstring>
3const int N = 1024;
4
5int hash[10] = {6, 6, 2, 6, 4, 4, 4, 8, 4, 6};
6int one_digit_hash[10] = {1, 1, 2, 6, 4, 2, 2, 4, 2, 8};
7
8int last_digit(char str[N], int st, int to)
9{
10 int i, tmp = 0, ret, num_of_five = 0;
11
12 if (st == to)
13 return one_digit_hash[str[st]-'0'];
14
15 ret = hash[str[to]-'0'];
16 for (i = st; i <= to; i++)
17 {
18 tmp = tmp * 10 + str[i] - '0';
19 str[i] = tmp / 5 + '0';
20 tmp %= 5;
21 num_of_five = (num_of_five * 10 + str[i] - '0') % 4;
22 }
23 if (str[st] == '0') st++;
24 ret = last_digit(str, st, to) * ret % 10;
25 while (num_of_five--) ret = ret * 8 % 10; //mul one 5 equals mul one 8
26
27 return ret;
28}
29
30int main()
31{
32 char str[N];
33
34 while (scanf("%s", str) == 1)
35 printf("%d\n", last_digit(str, 0, strlen(str) - 1));
36
37 return 0;
38}
39
posted on 2009-03-29 21:00
sdfond 阅读(663)
评论(2) 编辑 收藏 引用 所属分类:
Algorithm - Number Theory