题目:
N的阶乘定义为:N!=N×(N-1)×……×2×1
请编写一个程序,输出N的阶乘的十进制表示中从最末一个非0位开始,
自低位向高位数的第M位。
其中:0<=N<=10000,1<=M<=5
例如:N=5,M=2,结果是1(5!=120) N=8,M=3,结果为0(8!=40320)
输入:
第一行一个整数K (1<=K<=100),代表测试数据的个数;
接下来K行,每行两个整数N,M
输出:
输出K行,每行一个整数,即测试数据的结果。
样例:
输入:
2
5 2
8 3
输出:
1
0
这个题目的链接:点击打开链接
题目很简洁,但是很有意思啊。
首先,10000!太大,肯定是边乘边取模。
但是模多少呢?10000!的末尾的0的个数可是很多的,不可能先模一个很大的数,再数有多少个0。
这个题目,我认为必须先求出,n!末尾的0的个数d。
其实就是求d,使得5^d | n! 成立但5^(d+1) | n!不成立。
函数degree_in_fact就是专门干这个的,而且代码简单的可怕。
原理:
画一个n行n列的矩阵,第i(i从1到n)行第j(j从1到n)列表示i是否是p^j的倍数,是它的倍数就是1,不是就是0
其实远远不需要n列,但是我只是给出定理的证明,和空间效率无关。
下面考虑这个矩阵的和。
一方面,每一行的和表示的是i里面的p的次数,比如第p^3行,前面3个为1,后面的都是0,所以这一行的和为3,即p的次数。
所以矩阵的和是n!中p的次数。
另一方面,每一列的和是,从1到n有多少个数是p^j的倍数,所以答案为n/(p^j)
综上,n!中p的次数为n/p+n/(p^2)+n/(p^3)+......当j足够大时n/(p^j)=0
请注意,这个表达式是无法用递归来写的,因为这是整数的除法,不是实数的除法。
不过很巧的是,刚好有这么一个定理:
n/p^j/p=n/p^(j+1)
不要以为这个很显然,本文所有的除法都是整数的除法,也就是5/2会得到2,和实数的不同。
定理的证明:
假设右边的n/p^(j+1)=k,即k*p^(j+1)<=n<(k+1)*p^(j+1)
那么k*p<=n/p^j<(k+1)*p,那么k<=n/p^j/p<k+1
即n/p^j/p=k,证毕。
现在,表达式可以改写了,n!中p的次数为n/p+n/p/p+n/p/p/p+......
这个式子就可以用递归非常简单的写出来了。
degree_in_fact(n, 5)的值就是n!末尾的0的个数。
算出来0的个数为num之后,又要怎么做呢?
从1到n累乘,在乘之前,去掉num个2和num个5,剩下所有的数累乘,同时模100000即可。
这样就可以得到n!除掉后缀0之后的的最后5位
(这5位除了最后一位肯定不是0之外,都有可能是0,事实上还有可能都是0)
代码:
#include<iostream> using namespace std; int degree_in_fact(int m, int p)//求m!中素数p的次数 { if (m)return degree_in_fact(m / p, p) + m / p; return 0; } int main() { int k, n, m; cin >> k; while (k--) { cin >> n >> m; int num = degree_in_fact(n, 5); int fact = 1; for (int i = 1; i <= n; i++) { int j = i; while (j % 2 == 0 && num) { j /= 2; num--; } while (j % 5 == 0)j /= 5; fact = fact*j % 100000; } m--; while (m--)fact /= 10; cout << fact % 10 << endl; } return 0; }