阶乘(Factorial)是个很有意思的函数,但是不少人都比较怕它,我们来看看两个与阶乘相关的问题。
1) 给定一个整数N,那么N的阶乘N!末尾有多少个0呢?例如:N=10,N!=3628800,N!的末尾有两个0。
2) 求N!的二进制表示中最低位1的位置。
【分析】
对于问题1有些人碰到这样的题目会想:是不是要完整计算出N!的值?如果溢出怎么办?事实上,如果我们从"哪些数相乘能得到10"这个角度来考虑,问题就变得简单了。
首先考虑,如果N!=K*10M,且K不能被10整除,那么N!末尾有M个0。再考虑对N!进行质因数分解,N!=2X*3Y*5Z*...,由于10=2*5,所以M只跟X和Z有关,每一对2和5相乘可以得到一个10,于是M=min(X,Z)。不难看出,X大于等于Z,因为能被2整除的数出现的频率比能被5整除的数高得多,所以把公式简化为M=Z。
对于问题2,要求的是N!中二进制表示中最低位1的位置。例如:给定N=3,N!=6,那么N!的二进制表示(1010)的最低位1在第二位。
判断最后一个二进制位是否为0:若为0,则将此二进制数右移一位,即为商值;反之,若为1,则说明这个二进制数是奇数,无法被2整除。
所以,这个问题实际上等同于求N!含有质因数2的个数。即答案等于N!含有质因数2的个数加1。
1. 问题1的解法一
要计算Z,最直接的办法,就是计算i(i=1,2,3,...,N)的因式分解中5的指数,然后求和,代码如下:
1 #include <iostream> 2 using namespace std; 3 4 int numZero(int n) 5 { 6 int ret = 0; 7 for (int i = 1; i <= n; i++) 8 { 9 int j = i; 10 while (j % 5 == 0) 11 { 12 ret++; 13 j /= 5; 14 } 15 } 16 return ret; 17 } 18 19 int main(int argc, char *argv[]) 20 { 21 int N; 22 while (cin >> N) 23 { 24 cout << numZero(N) << endl; 25 } 26 return 0; 27 }
2. 问题1的解法二
公式:Z = [N/5] + [N/52] + [N/53] + ... (不用担心这会是一个无穷的运算,因为总存在一个K,使得5K>N,[N/5K] = 0。)
公式中,[N/5]表示不大于5的数中5倍数贡献一个5,[N/52]表示不大于N的数中52的倍数再贡献一个5……参考代码如下:
1 #include <iostream> 2 using namespace std; 3 4 int numZero(int n) 5 { 6 int ret = 0; 7 while (n) 8 { 9 ret += n / 5; 10 n /= 5; 11 } 12 return ret; 13 } 14 15 int main(int argc, char *argv[]) 16 { 17 int N; 18 while (cin >> N) 19 { 20 cout << numZero(N) << endl; 21 } 22 return 0; 23 }
3. 问题2的解法一
由于N!中含有质因数2的个数,等于[N/2] + [N/4] + [N/8] + [N/16] + …… 则参考代码如下:
1 #include <iostream> 2 using namespace std; 3 4 int lowestOne(int n) 5 { 6 int ret = 1; 7 while (n) 8 { 9 n >>= 1; 10 ret += n; 11 } 12 return ret; 13 } 14 15 int main(int argc, char *argv[]) 16 { 17 int N; 18 while (cin >> N) 19 { 20 cout << lowestOne(N) << endl; 21 } 22 return 0; 23 }
4. 问题2的解法二
N!含有质因数2的个数,还等于N减去N的二进制表示中1的数目。我们还可以通过这个规律来求解,代码略。
相关题目
给定整数n,判断它是否为2的方幂。
【解答】参考答案如下:
1 n > 0 && (n & (n-1) == 0)