Sample Input
1
2
26
125
3125
9999
Sample Output
1 -> 1
2 -> 2
26 -> 4
125 -> 8
3125 -> 2
9999 -> 8
例如 N = 26,N! = 403291461126605635584000000,最后一位非零数字为 4。
对于 N <= 10000 的时候,可以这样做:
把 1 ~ N 的 2, 5 因子除尽,然后每个数都模 10。
对于一次查询 n (n <= N),有 n! = 2a * 5b * c, c 为所有非 2,5 因子的乘积,
那么 n! 的最后一个不是 10 的数字等于 2a-b * c % 10,
接下来就是统计 1 ~ n 中 3,7,9 的个数,以及 因子 2 和 5 的个数,然后,
利用 (a * b) % 10 = (a % 10) * (b % 10) ,以及
2 4 % 10 = 2
3 4 % 10 = 1
7 4 % 10 = 1
9 4 % 10 = 1
可以化简计算。
using namespace std;
int main ()
{
const int N = 10001 ;
int num [N];
int cnt [ 10 ];
for ( int i = 0 ; i < N; i ++ )
num [i] = i;
for ( int i = 5 ; i < N; i *= 5 )
{
for ( int j = i; j < N; j += i)
num [j] /= 5 ; // remove all the factor 5
}
for ( int i = 2 ; i < N; i *= 2 )
{
for ( int j = i; j < N; j += i)
num [j] /= 2 ; // remove all the factor 2
}
for ( int i = 1 ; i < N; i ++ )
num [i] %= 10 ;
int map2 [ 4 ] = { 6 , 2 , 4 , 8 }; // map2 [i] = 2^(i+4) % 10
int map3 [ 4 ] = { 1 , 3 , 9 , 7 };
int map7 [ 4 ] = { 1 , 7 , 9 , 3 };
int map9 [ 4 ] = { 1 , 9 , 1 , 9 };
int n;
while (scanf ( " %d " , & n) != EOF)
{
if (n == 1 )
{
printf ( " 1 -> 1 " ); // when n == 1, it's an exception
continue ;
}
cnt [ 3 ] = cnt [ 7 ] = cnt [ 9 ] = 0 ;
for ( int i = 1 ; i <= n; i ++ )
cnt [num [i]] ++ ;
cnt [ 3 ] %= 4 ;
cnt [ 7 ] %= 4 ;
cnt [ 9 ] %= 4 ;
cnt [ 2 ] = cnt [ 5 ] = 0 ;
for ( int i = 2 ; i <= n; i *= 2 )
cnt [ 2 ] += n / i; // count the factor 2 in n!
for ( int i = 5 ; i <= n; i *= 5 )
cnt [ 5 ] += n / i; // count the factor 5 in n!
cnt [ 2 ] = (cnt [ 2 ] - cnt [ 5 ]) % 4 ;
printf ( " %5d -> %d " , n, map2 [cnt [ 2 ]] * map3 [cnt [ 3 ]] * map7 [cnt [ 7 ]] * map9 [cnt [ 9 ]] % 10 );
}
return 0 ;
}
/*
Run ID User Problem Result Memory Time Language Code Length Submit Time
2945042 rappizit 1604 Accepted 204K 0MS G++ 1083B 2007-11-26 00:04:13
*/
以上是自己想到的,进行了 O(N) 时间 的预处理以及每次 O(n) 时间的查询,而且使用 O(N) 的空间。
对于 AC 本题已经足够,但是考虑到 N 上亿的时候就没有足够空间了。参考一下别人的算法。
============以下引用自 http://gz0531.org/sub/article.asp?id=3&page=1===================
比如
1 2 (3) 4 5 6 (7) 8 (9) 10 11 12 (13) 14 15 16 (17) 18 (19) 20 21 22 (23) 24 25 26
其中3个3,2个7和9。同时还有3个5,这里只考虑5结尾的5的倍数,因为末尾是0的在后面递归解决。
然后递归(循环其实也行)检查5的倍数:将所有5的倍数除以5,得到int(26/5)个数
1 2 (3) 4 5
我们又获得了一个3和一个5。
再检查5的倍数。数列只剩下1了。
1
再递归一次就啥都没有了——回归条件。
检查5倍数的递归结束。
好了,下面可以抛弃所有奇数了,开始处理偶数。
所有偶数提取2得到
1 2 (3) 4 5 6 (7) 8 (9) 10 11 12 (13)
到此进入下一层递归,将此数列进行同样操作。显然这些数列都是1到N的自然数列,只要传递数列最后
一个数就可以了,空间复杂度很低。回归条件就是这个数列被如此反复折磨得一个不剩。
============以上引用自 http://gz0531.org/sub/article.asp?id=3&page=1===================
于是写下如下代码:
using namespace std;
int cnt2, cnt3, cnt5, cnt7, cnt9;
int map2 [ 4 ] = { 6 , 2 , 4 , 8 }; // map2 [i] = 2^(i+4) % 10
int map3 [ 4 ] = { 1 , 3 , 9 , 7 };
int map7 [ 4 ] = { 1 , 7 , 9 , 3 };
int map9 [ 4 ] = { 1 , 9 , 1 , 9 };
void rec ( int n)
{
if ( ! n) return ;
for ( int m = n; m; m /= 5 )
{
int q = m / 10 , r = m % 10 ;
cnt3 += q + (r >= 3 );
cnt5 += q + (r >= 5 ); // count the num whose last digit is 5
cnt7 += q + (r >= 7 );
cnt9 += q + (r >= 9 );
}
cnt2 += n / 2 ;
rec (n / 2 );
}
int f ( int n)
{
if (n == 1 ) return 1 ;
cnt2 = cnt3 = cnt5 = cnt7 = cnt9 = 0 ;
rec (n);
// printf ("%d %d %d %d %d ", cnt2, cnt3, cnt5, cnt7, cnt9);
return map2 [(cnt2 - cnt5) % 4 ] * map3 [cnt3 % 4 ] * map7 [cnt7 % 4 ] * map9 [cnt9 % 4 ] % 10 ;
}
int main ()
{
int n;
while (scanf ( " %d " , & n) != EOF)
{
printf ( " %5d -> %d " , n, f (n));
}
return 0 ;
}
/*
Run ID User Problem Result Memory Time Language Code Length Submit Time
2945147 rappizit 1604 Accepted 168K 0MS G++ 896B 2007-11-26 01:40:2
*/
每次查询的时间复杂度是 O(log2n * log5n),空间复杂度为 O(1)。其中 log2n 是用于递归,log5n 是用于每层
递归把因子 5 “消灭”。
这种算法在查询比较多的时候其实比前一种算法慢,但是空间复杂度可是大大降低!
那么假如 当 N 达到 10100 的时候呢?例如这道题:
http://acm.hziee.edu.cn/showproblem.php?pid=1066
每次查询可能要执行上百万次操作(涉及高精度运算),有没有更好的算法?很幸运搜到了,呵呵,其实在
tenshi 例程里面就有代码,不过光是看代码是很难看懂其中的奥妙的。
以下是自己的描述,参考了http://mcqsmall.yculblog.com/post.1249397.html 。
首先对数列 d [10] = {1, 1, 2, 3, 4, 1, 6, 7, 8, 9} 和
ff [10] = {1, 1, 2, 6, 4, 4, 4, 8, 4, 6}
有 d [0] * ... * d [i] % 10 = ff [i],0 <= i < 10。
对于 n < 5 直接输出 ff [n] 即可。
对于 n >= 5,例如 n = 26,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
的乘积等于
1 2 3 4 1 6 7 8 9 1 11 12 13 14 1 16 17 18 19 1 21 22 23 24 1 26
的乘积再乘上 5 10 15 20 25 的乘积,而5 10 15 20 25 的乘积等于
1~26/5 的乘积再乘上 526/5。
先考虑
1 2 3 4 1 6 7 8 9 1 11 12 13 14 1 16 17 18 19 1 21 22 23 24 1 26,
可以 10 个 10 个地分成几组。
其中 1 2 3 4 1 6 7 8 9 1,11 12 13 14 1 16 17 18 19 1 这两组数的乘积
都为 10*q + 6 的形式,两个乘积乘起来还是 10*q + 6 的形式。
而 21 22 23 24 1 26 这组数的乘积则是 10*q + ff [26%10] = 10*q + 4的形式,
因此 3 组数的乘积是 10*q + 4 的形式,这个 4 由 ff [26%10] * 6 % 10 所得。
因此 26! = (26/5)! * 526/5 * (10*q+4) = (26/5)! * 1026/5 * [(10*q+4) / 226/5],
则 26! 的最后一个非零数字为 (26/5)! * [(10*q+4) / 226/5]。
注意到除了0! 和 1!,阶乘的最后一个非零数字必为偶数,所以有一个规律:
(10*q + 2) / 2 = 10*q' + 6
(10*q + 6) / 2 = 10*q' + 8
(10*q + 8) / 2 = 10*q' + 4
(10*q + 4) / 2 = 10*q' + 2
每除以 2 四次,尾数就循环一次。因此
(10*q+4) / 226/5 的尾数即 (10*q+4) / 226/5%4,这个可以用以下代码计算:
t = ff [n % 10] * 6 % 10;
for (int i = 1; i <= n / 5 % 4; i ++)
{
if (t == 2 || t == 6) t += 10;
t /= 2;
}
计算出来的 t 即为 (10*q+4) / 2^(26/5) 的尾数,然后用 t 乘以 (26 / 5)! 的最后
一个非零数字再对 10 取模即得到 26! 的最后一个非零数字而计算 (26 / 5)! 的最后
一个非零数字可以使用递归处理。
综上,设 F(N) 为 N! 最后一个非零数字,则有以下递归式:
F(N) = ff [N] (N < 5)
F([N/5]) * ff [N的尾数] * 6
F(N) = ----------------------------------- (N >= 5)
2[N/5] % 4
因此算法的时间复杂度是 O(log5N) 的。
即使 N 达到 10100,也可以很快计算出来,不过需要使用高精度整数,整除 5 也即
乘以 2 再整除 10,而乘以 2 也即自加,整除 10 也即截掉最后一位数字。
而对 4 取模只要取最后两位数字对 4 取模即可,例如 1234 % 4 = 34 % 4 = 2。
因此实现起来相当方便。
using namespace std;
const int ff [ 10 ] = { 1 , 1 , 2 , 6 , 4 , 4 , 4 , 8 , 4 , 6 };
int f ( int n)
{
if (n < 5 ) return ff [n];
int t = ff [n % 10 ] * 6 % 10 ;
for ( int i = 1 , r = n / 5 % 4 ; i <= r; i ++ )
{
if (t == 2 || t == 6 ) t += 10 ;
t /= 2 ;
}
return f (n / 5 ) * t % 10 ;
}
int main ()
{
int n;
while (scanf ( " %d " , & n) != EOF)
{
printf ( " %5d -> %d " , n, f (n));
}
return 0 ;
}
/*
Run ID User Problem Result Memory Time Language Code Length Submit Time
2945954 rappizit 1604 Accepted 168K 0MS G++ 435B 2007-11-26 13:46:54
*/
哈,没空再写一个高精度的,所以只在 POJ 上重新提交一次以验证正确性而已。