题目来源: http://acm.pku.edu.cn/JudgeOnline/problem?id=1604
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! = 2
a * 5
b * c, c 为所有非 2,5 因子的乘积,
那么 n! 的最后一个不是 10 的数字等于 2
a-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
可以化简计算。
#include
<
cstdio
>
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===================
于是写下如下代码:
#include
<
cstdio
>
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。
因此实现起来相当方便。
#include
<
cstdio
>
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 上重新提交一次以验证正确性而已。