计算阶乘n!末尾所含0的个数

时间:2023-02-01 18:48:57

From : http://www.chinaunix.net/jh/23/926848.html

 

[color=Orange]问题描述[/color]
    给定参数n(n为正整数),请计算n的阶乘n!末尾所含有“0”的个数。
    例如,5!=120,其末尾所含有的“0”的个数为1;10!= 3628800,其末尾所含有的“0”的个数为2;20!= 2432902008176640000,其末尾所含有的“0”的个数为4。
 
[color=Orange]计算公式[/color]
    这里先给出其计算公式,后面给出推导过程。
    令f(x)表示正整数x末尾所含有的“0”的个数,则有:
      当0 < n < 5时,f(n!) = 0;
      当n >= 5时,f(n!) = k + f(k!), 其中 k = n / 5(取整)。
 
[color=Orange]问题分析[/color]
    显然,对于阶乘这个大数,我们不可能将其结果计算出来,再统计其末尾所含有的“0”的个数。所以必须从其数字特征进行分析。下面我们从因式分解的角度切入分析。
 
    我们先考虑一般的情形。对于任意一个正整数,若对其进行因式分解,那么其末尾的“0”必可以分解为2*5。在这里,每一个“0”必然和一个因子“5”相对应。但请注意,一个数的因式分解中因子“5”不一定对应着一个“0”,因为还需要一个因子“2”,才能实现其一一对应。
 
    我们再回到原先的问题。这里先给出一个结论:
    结论1: 对于n的阶乘n!,其因式分解中,如果存在一个因子“5”,那么它必然对应着n!末尾的一个“0”。
    下面对这个结论进行证明:
    (1)当n < 5时, 结论显然成立。
    (2)当n >= 5时,令n!= [5k * 5(k-1) * ... * 10 * 5] * a,其中 n = 5k + r (0 <= r <= 4),a是一个不含因子“5”的整数。
    对于序列5k, 5(k-1), ..., 10, 5中每一个数5i(1 <= i <= k),都含有因子“5”,并且在区间(5(i-1),5i)(1 <= i <= k)内存在偶数,也就是说,a中存在一个因子“2”与5i相对应。即,这里的k个因子“5”与n!末尾的k个“0”一一对应。
    我们进一步把n!表示为:n!= 5^k * k! * a(公式1),其中5^k表示5的k次方。很容易利用(1)和迭代法,得出结论1。
    
    上面证明了n的阶乘n!末尾的“0”与n!的因式分解中的因子“5”是一一对应的。也就是说,计算n的阶乘n!末尾的“0”的个数,可以转换为计算其因式分解中“5”的个数。
 
    令f(x)表示正整数x末尾所含有的“0”的个数, g(x)表示正整数x的因式分解中因子“5”的个数,则利用上面的的结论1和公式1有:
       f(n!) = g(n!) = g(5^k * k! * a) = k + g(k!) = k + f(k!)
所以,最终的计算公式为:
    当0 < n < 5时,f(n!) = 0;
    当n >= 5时,f(n!) = k + f(k!), 其中 k = n / 5(取整)。
 
[color=Orange]计算举例[/color]
   f(5!) = 1 + f(1!) = 1
   f(10!) = 2 + f(2!) = 2
   f(20!) = 4 + f(4!) = 4
   f(100!) = 20 + f(20!) = 20 + 4 + f(4!) = 24
   f(1000!) = 200 + f(200!) = 200 + 40 + f(40!) = 240 + 8 + f(8!) = 248 + 1 + f(1) =249
   ...
 
终于写完了,困S了:)



 yg 回复于:2007-04-21 16:35:30

也算递归吧


 win_hate 回复于:2007-04-21 16:43:35

Pot_p(n!) = [n/p] + Pot_p([n/p]!)

递归使用这个公式就可以得到 Pot_p(n!) 的表达式。

可以参考柯召,孙琦的 《数论讲义》上册,数论函数部分。


 tyc611 回复于:2007-04-21 16:48:41

引用: 原帖由 win_hate 于 2007-4-21 16:43 发表
Pot_p(n!) = [n/p] + Pot_p([n/p]!)

递归使用这个公式就可以得到 Pot_p(n!) 的表达式。

可以参考柯召,孙琦的 《数论讲义》上册,数论函数部分。 


恩,我上面推出的公式就是这个,p为5吧?
哎,又吃了数字的亏了:em10:
手头没这本书,看能不能找到


 圆点坐标 回复于:2007-04-21 16:53:09

我去年去学校招聘的时候,给应届生出了这个题目,只有2个人的想法靠近这个答案。


 win_hate 回复于:2007-04-21 16:54:50

引用: 原帖由 tyc611 于 2007-4-21 16:48 发表

恩,我上面推出的公式就是这个,p为5吧?
哎,又吃了数字的亏了:em10:
手头没这本书,看能不能找到 



是的。


我以前写过一个资料,复制到这里吧:

......
另一很精致的方法来自柯召的 “数论讲义”,把 ord_p 简记为 ord:

    * ord(ab)=ord(a)+ord(b) 
       
    * ord(n!) = [n/p] + ord( [n/p]! ),因为:
      ord(n!) 
      = ord(1) + ... + ord(n) 
      = ord(p) + ord(2p) + ... + ord([n/p]p) 
      = (ord(1) + ord(p)) + (ord(2)+ord(p)) + ... + (ord([n/p])+ord(p)) 
      = (ord(1) +1) +(ord(2)+1) +...+(ord([n/p])+1) 
      = [n/p]+ord( [n/p]! )

    *  [ [n/p]/p ] = [n/p^2]
      若把 n 写成 p 进制表达,则上面这个式子很好理解 

现在只要递归地调用 ord(n!) = [n/p] + ord( [n/p]! ) 就可以得到 ord(n!) 的表达式:

[n/p]+[n/p^2]+[n/p^3] +....


 awake 回复于:2007-04-21 18:23:54

赞排版。


 ArXoR 回复于:2007-04-21 19:42:50

一个和此问题有点关系的有趣问题是:
给定质数p, 求杨辉三角第n行有多少个元素被p整除.


 win_hate 回复于:2007-04-21 22:18:31

引用: 原帖由 ArXoR 于 2007-4-21 19:42 发表
一个和此问题有点关系的有趣问题是:
给定质数p, 求杨辉三角第n行有多少个元素被p整除. 



嗯,柯召那本书的 pot 一节最后一个定理就是求 pot_p ( binomial(n, r) )  的表达式.


 ArXoR 回复于:2007-04-21 22:49:21

引用: 原帖由 win_hate 于 4/21/07 22:18 发表


嗯,柯召那本书的 pot 一节最后一个定理就是求 pot_p ( binomial(n, r) )  的表达式. 



pot_p ( binomial(n, r) ) 有close from? 我只知道级数表达的形式.
不过不妨碍我说的这个问题的解决, 呵呵.


 win_hate 回复于:2007-04-22 00:19:43

引用: 原帖由 ArXoR 于 2007-4-21 22:49 发表


pot_p ( binomial(n, r) ) 有close from? 我只知道级数表达的形式.
不过不妨碍我说的这个问题的解决, 呵呵. 



不是 close 的,一个关系式吧,在你给的这个题目用不上。

考虑 pot_p( binomial (n, r)),

[n/p^i] >=  [ (n-r)/p^i] + [r/p^i]. 如果 p  不整除 binomial(n, r), 意味着对所有的 i,等号都成立.

设 n = (n_m ... n_0 )_p, r = (r_s ... r_0)_p, 等号成立要求

n_m ... n_i = [n_m ... n_i . n_{i-1} ... n_0 - r_s ... r_i . r_{i-1} ... r_0] + r_s ... r_i 
= n_m ... n_i + [0. n_{i-1} ... n_0 - 0. r_{i-1} ... r_0 ]

也就是

 [0. n_{i-1} ... n_0 - 0. r_{i-1} ... r_0 ] =  0.

即对每个 i,  n mod p^i >= r mod p^i,能被 p 整除的个数为 

n + 1 - (n_m+1)...(n_0+1).

[  本帖最后由 win_hate 于 2007-4-22 00:31 编辑 ]


 ArXoR 回复于:2007-04-22 00:39:08

引用: 原帖由 win_hate 于 4/22/07 00:19 发表


不是 close 的,一个关系式吧,在你给的这个题目用不上。

考虑 pot_p( binomial (n, r)),

[n/p^i] >=  [ (n-r)/p^i] + [r/p^i]. 如果 p  不整除 binomial(n, r), 意味着对所有的 i,等号都成立.

 ... 



Bingo... :)


 yuanchengjun 回复于:2007-04-22 09:38:33

#include <stdio.h>
#include <string.h>
#include <stdint.h>

int main(void)
{
uint32_t count = 0;
uint32_t tmp;
uint32_t n;

for (n = 1; n <= 1000; n ++)
{
tmp = n;
while((0 == (tmp % 5)) && (tmp > 0))
{
tmp /= 5;
count ++;
}
}

printf("%d", count);

return 0;
}


 tyc611 回复于:2007-04-22 16:35:44

win_hate和ArXoR兄的回复太深奥了,发晕中。。。


 tyc611 回复于:2007-04-22 16:40:01

引用: 原帖由 yuanchengjun 于 2007-4-22 09:38 发表
#include <stdio.h>
#include <string.h>
#include <stdint.h>

int main(void)
{
uint32_t count = 0;
uint32_t tmp;
uint32_t n;

for (n = 1; n <= 1000; n ++)
{
t ... 


你这个太慢了,看看我上面的示例计算1000时才几步,你的步进方式有问题,还有应该利用前面已经计算的结果

我写个上面公式的实现吧,收敛速度很快的:5^x = num / 5 * 5,x递归或者迭代次数
(一个递归,一个迭代)


#include <stdio.h>



int f_r(int num)

{

    if (num < 5)

        return 0;

    else

        return (num / 5) + f_r(num / 5);

}



int f_i(int num)

{

    int count = 0;

    for (; num >= 5; num /= 5)

        count += num / 5;

    return count;

}



int main(void)

{

    int num = 1000;

    printf("%d/n", f_r(num));

    printf("%d/n", f_i(num));

        

    return 0;






 openq 回复于:2007-04-24 12:14:03

LZ的方法部可取,N很大时n/5的阶乘就溢出了!


 tyc611 回复于:2007-04-24 12:20:51

引用: 原帖由 openq 于 2007-4-24 12:14 发表
LZ的方法部可取,N很大时n/5的阶乘就溢出了! 


不好意思,可能是我公式没写好,其实根本就不计算阶乘的

看下我15楼给的算法实现就明白了

[  本帖最后由 tyc611 于 2007-4-24 12:22 编辑 ]


 yuanchengjun 回复于:2007-04-24 12:35:30

引用: 原帖由 圆点坐标 于 2007-4-21 16:53 发表
我去年去学校招聘的时候,给应届生出了这个题目,只有2个人的想法靠近这个答案。 




要求太高了,只要计算结果正确,就应该算通过。


算法有很多,题目没有给出“最优算法”的信息,
如果那样,假设满分10分,凡正确的6分,剩下4分安计算复杂度给分,
最优的或者达到什么程度以上的10分。


计算机和数学不一样的,有时候要考虑具体运算环境。
1,计算机除了算法以外要考虑速度和时间。
2,有的时候,正确性和稳定性比优秀算法、高速更重要。
3,有的时候,多循环10次,比多一次递归调用节省时间。


运算速度:
1,公式。
2,枚举,只举出有效情况。不是枚举所有,再判断是否有效。
3,枚举,尽可能缩小有效情况范围。
4,枚举所有。最无奈的情况。


有的时候想不出来,有的时候时间有限,而且我不是大侠:-(
想不出公式就枚举;枚举的时候尽可能缩小范围;
如果不知到缩小范围条件,就枚举所有……
想这些东西需要一定的基础,还有时间的,……

[  本帖最后由 yuanchengjun 于 2007-4-24 12:53 编辑 ]


 ArXoR 回复于:2007-04-24 12:58:53

引用: 原帖由 yuanchengjun 于 4/24/07 12:35 发表



要求太高了,只要计算结果正确,就应该算通过。


算法有很多,题目没有给出“最优算法”的信息,
如果那样,假设满分10分,凡正确的6分,剩下4分安计算复杂度给分,
最优的或者达到什么程度以上的10分 ... 



这是在知道可以有更优算法的情况下才有这些选择...
连知道都不知道的话在应用规模很大的时候就完全没有办法了.
就像如果知道可以用Simplex Algorithm去做线性规划至少应该知道线性规划是有P类算法的.

而且基本上没法要求最优算法, 求解并证明最坏复杂度紧下界不是一件简单的事情.


 babyfrog 回复于:2007-04-24 13:13:57

ACM里面这么写会time out否


 yuanchengjun 回复于:2007-04-24 13:15:32

同意楼上,一般这些算法需要千锤百炼,好的软件公司会把类似这些东西沉淀下来,要用调一下就OK。
再帖一次,按照楼主算法。



#include <stdio.h>
#include <stdint.h>

int main(void)
{
    uint32_t count;
    uint32_t n;

    n = 1000;
    count = 0;
    while (n > 4)
        count += n /= 5;

    printf("%d/n", count);

    return 0;
}

[  本帖最后由 yuanchengjun 于 2007-4-24 13:17 编辑 ]


 slay78 回复于:2007-04-24 14:06:56

从2开始,阶乘的最后一位数字非零数字都是偶数,所以最后一位肯定不是5,那么这些偶数只有碰到5或者10乘起来才会再多一个0


 thinkinnight 回复于:2007-04-24 15:16:19

引用: 原帖由 win_hate 于 2007-4-21 16:54 发表


是的。


我以前写过一个资料,复制到这里吧:

......
另一很精致的方法来自柯召的 “数论讲义”,把 ord_p 简记为 ord:

    * ord(ab)=ord(a)+ord(b) 
       
    * ord(n!) = [n/p] + ord( [n ... 



 = ord(1) + ... + ord(n)
      = ord(p) + ord(2p) + ... + ord([n/p]p) 

对于这一步不知道是怎么转的


 win_hate 回复于:2007-04-24 17:32:55

引用: 原帖由 thinkinnight 于 2007-4-24 15:16 发表


 = ord(1) + ... + ord(n)
      = ord(p) + ord(2p) + ... + ord([n/p]p) 

对于这一步不知道是怎么转的 



如果 a 不是 p 的倍数,则 ord(a)=0。 把值为 0 的项去掉,剩下的就只有 pk 形式的了,再估计一下 k 的范围即可。


 shhgs 回复于:2007-04-24 19:54:37

Py code

def zeros(n) :

    result = 0

    n = n / 5

    while n != 0 :

        result += n

        n = n / 5

    return result



That's so simple. Primary school's math problem.


 thinkinnight 回复于:2007-04-24 21:31:30

哦,精巧


 局外人 回复于:2007-04-24 22:10:22


(define (f n p r) 

(let ((t (quotient n p)))

(if (> n 0) (f t p (+ r t)) r)))




 shhgs 回复于:2007-05-02 11:44:41

10 = 2 * 5

由于在[1,n]中,2的因子远远多于5的因子,因此只要计算[1,n]当中有多少5的因子就能知道n!的末尾有多少连续的0了。

那么[1,n]当中有多少5的因子呢?

假设,现在有一个p, 使得 5 ** p <= n < 5 ** (p + 1) ,
则, [1, n]中,能整除5的数有 n / 5,整除25的数有 n / 25 , ... , n / (5 ** p)   -- 这里的除号作整除讲
则 [1, n]中,5的因子共有 n / 5 + n / 25 + n / 125 + ... + n / (5 **p) 个
 
只不过是小学数学竞赛的题目,用得着动用那么吓人的数学知识吗?


 cjaizss 回复于:2007-05-02 12:44:27

太easy了,

int how_much_0(int i)

{

     int ret=0;

     while(i) {

             i /= 5;

           ret += i;

     }

      return ret;

}




 局外人 回复于:2007-05-02 13:42:08

引用: 原帖由 shhgs 于 2007-5-2 11:44 发表
只不过是小学数学竞赛的题目,用得着动用那么吓人的数学知识吗?
 ... 



同样一个题目,小学生有小学生的做法,大学生有大学生的做法,这是个层次的问题......


 bitzilla 回复于:2007-05-11 15:56:56

殊途同归,


 oopos 回复于:2007-10-20 20:05:01

不错,分析的很经典!!!
呵呵,我原来是这样想的,
把所得 的乘积分解质因式,则2的个数肯定比5多,而又只有:
2x5才能产生末0..
因此:
yinzi = 0 ;
for(; n ;) {n/=i ; yinzi += n ;} 

其中:i=5 ;


 feiyang21687 回复于:2007-10-20 20:46:03

LZ的递归式麻烦了:
多少个零可以算包含有多少个5,多少个25,多少个5^3...
f(a!) = a / 5 + 2 * (a/25) + 3*(a/5^3) + ...


 win_hate 回复于:2007-11-05 18:57:46

引用: 原帖由 ArXoR 于 2007-4-21 19:42 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=6708836&ptid=926848]计算阶乘n!末尾所含0的个数
一个和此问题有点关系的有趣问题是:
给定质数p, 求杨辉三角第n行有多少个元素被p整除. 




我在工作中居然真的碰到了这个问题,:shock: 

这个东西跟所谓的 lucas 公式有关,如果 

计算阶乘n!末尾所含0的个数
计算阶乘n!末尾所含0的个数


计算阶乘n!末尾所含0的个数

我们老大给了个漂亮的证明,在这里共享一下:

在 F_p[z] 上展开 (1+z)^n, 有 

计算阶乘n!末尾所含0的个数
计算阶乘n!末尾所含0的个数

对比一下两边的系数即可。