利用阶乘因子数公式证明素数无穷多

    搞过 OI/ACM 的同学们想必对一道经典题目印象极深:求 n 的阶乘末尾有多少个 0 。注意到末尾的一个 0 是由一个因子 2 和一个因子 5 相乘产生的;但在 n 的阶乘里,因子 5 的个数通常远远少于因子 2 。因此这个问题就等价于问 n 的阶乘里有多少个因子 5 。在 n 的阶乘式中,每个 5 的倍数里都含有一个因子 5 ,每个 25 的倍数里都还含有另外一个因子 5 ,每个 125 的倍数里都还有第三个因子 5 ……因此, n 的阶乘里因子 5 的个数的计算公式就是 ⌊n/5⌋ + ⌊n/25⌋ + ⌊n/125⌋ + … 。如果把 K 的阶乘里素因子 p 的个数记作 Φ(p, K) ,则 Φ(p, K) = Σ⌊K/(p^i)⌋ 。有意思的是,最近 The American Mathematical Monthly 上的一篇文章利用这个公式瞬间证明了素数无穷多的定理。

    如果素数是有限的,则 K 的阶乘就可以写成所有 p^Φ(p, K) 的乘积,其中的 p 取遍所有的素数。注意到 Φ(p, K) = Σ⌊K/(p^i)⌋ < Σ(K/(p^i)) = K/(p-1) < K ,因此对任意正整数 K 都有 K! < (Πp)^K ,其中 Πp 表示所有素数的乘积。但当 K 充分大的时候, K! 显然会超过一个常数的 K 次方,矛盾。因此素数不可能是有限的。   来源:http://www.cut-the-knot.org/wiki-math

计算阶乘的另一些有趣的算法

    一个正整数n的阶乘就是前n个正整数的乘积,我们通常需要n-1次乘法操作来算出精确的值。不像等差数列求和、a的n次幂之类的东西,目前求阶乘还没有什么巨牛无比的高效算法,我们所能做的仅仅是做一些小的优化。

更少的乘法运算次数?
    在高精度运算中,乘法计算的速度远远慢于加减法,因此我们有必要减少乘法运算的次数。下面我将做一个非常简单的变换,使得计算阶乘只需要n/2次乘法。继续看下去之前,你能自己想到这个算法来吗?

    我们可以把一个数的阶乘转换为若干个平方差的积。例如,假如我想求9!,我可以把前9个正整数的乘积写成这个样子:
   1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9
= (5-4) * (5-3) * (5-2) * (5-1) * 5 * (5+1) * (5+2) * (5+3) * (5+4)
= (5-1) * (5+1) * (5-2) * (5+2) * (5-3) * (5+3) * (5-4) * (5+4) * 5
= (5^2 – 1^2) * (5^2 – 2^2) * (5^2 – 3^2) * (5^2 – 4^2) * 5
    注意到一个有趣的事实:上面的四个平方差算出来分别是24, 21, 16, 9,它们之间的差正好是连续的奇数(因为n^2等于前n个正奇数的和)。因此,我们可以用初始数(n/2)^2不断减去一个个的正奇数,求出所有n/2个平方差,再用n/2次乘法把它们乘起来。这种算法实现起来非常简单,并且(当n不大时)同样只需要单精度乘高精度,但需要的乘法次数大大减少了。假设我们已经有了一个高精度类,求n!只需要下面几句话:
long h=n/2, q=h*h;
long r = (n&1)==1 ? 2*q*n : 2*q;
f = LargeInteger.create(r);
for(int d=1; d<n-2; d+=2)
   f = f.multiply(q-=d);

更少的总运算次数?
    尽量提取阶乘中的因子2,我们可以得到另一种阶乘运算的优化方法。这很可能是不需要分解质因数的阶乘算法中最快的一种。
    假如我们需要计算20!,我们可以把20拆成若干组正奇数的乘积:

  1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11 * 12 * 13 * 14 * 15 * 16 * 17 * 18 * 19 * 20
= 1 * 3 * 5 * 7 * 9 * 11 * 13 * 15 * 17 * 19 * 2 * 4 * 6 * 8 * 10 * 12 * 14 * 16 * 18 * 20
= 1 * 3 * 5 * 7 * 9 * 11 * 13 * 15 * 17 * 19 * 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 2^10
= 1 * 3 * 5 * 7 * 9 * 11 * 13 * 15 * 17 * 19 * 1 * 3 * 5 * 7 * 9 * 2 * 4 * 6 * 8 * 10 * 2^10
= 1 * 3 * 5 * 7 * 9 * 11 * 13 * 15 * 17 * 19 * 1 * 3 * 5 * 7 * 9 * 1 * 2 * 3 * 4 * 5 * 2^15
= 1 * 3 * 5 * 7 * 9 * 11 * 13 * 15 * 17 * 19 * 1 * 3 * 5 * 7 * 9 * 1 * 3 * 5 * 2 * 4 * 2^15
= 1 * 3 * 5 * 7 * 9 * 11 * 13 * 15 * 17 * 19 * 1 * 3 * 5 * 7 * 9 * 1 * 3 * 5 * 1 * 2 * 2^17
= 1 * 3 * 5 * 7 * 9 * 11 * 13 * 15 * 17 * 19 * 1 * 3 * 5 * 7 * 9 * 1 * 3 * 5 * 1 * 2^18

    只需要一次累乘就可以求到每一组奇数的乘积,最后再花费log(n)次乘法把它们全部乘起来。最后的那个2^18也可以二分计算出来。真正的代码还有很多细节上的优化,另外还借用了递归使得操作变得更加简便。你可以在本文最后附的那个链接里去找Split-Recursive算法。

还能再快一点么?
    继续扩展上面的算法,我们可以想到,如果把每个数的质因数都分解出来,并且统计每种质因子有多少个,我们就可以多次使用二分求幂,再把它们的结果乘起来。注意这里并不是真的要老老实实地去分解每个数的质因子。对于每个质数x,我们可以很快算出前n个正整数一共包含有多少个质因子x(记得如何求n!末尾有多少个0么)。这种算法的效率相当高,已经能够满足大多数人的需要了。

另一种诡异的阶乘算法:
    这个算法可能是所有有名字的阶乘算法中最慢的一个了(Additive Moessner算法),它对一个数列进行重复的累加操作,一次次地计算前缀和,总共将花费O(n^3)次加法操作。但是,令人费解的是,这个简单的程序为什么可以输出前n个正整数的阶乘呢?
a[0]:=1;
for i:=1 to n do
begin
   a[i]:=0;
   for j:=n downto 1 do
   begin
      for k:=1 to j do
         a[k]:=a[k]+a[k-1]
      write(a[i],' ');
   end;
end;

    我在网上搜索相关的东西时找到了另一个有趣的东西。对一个初始时全为1的数列反复进行这两个操作:累加求前缀和,然后以1,2,3,…的间隔划掉其中一部分数(即划去所有位置编号为三角形数的数)形成新的序列。类似的数列操作方法最先由Alfred Moessner提出的,我们这里不妨把它叫做Moessner数列。你会发现,第n轮操作开始前,数列的第一个数恰好是n! 。看看下面的例子吧:

1 1 1 1 1 1 1 1 1  1  1  1  1  1  1 …
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 …
x 2 x 4 5 x 7 8 9  x 11 12 13 14  x …

2 4  5  7  8  9 11 12 13 14 …
2 6 11 18 26 35 46 58 71 85 …
x 6  x 18 26  x 46 58 71  x …

6 18 26 46  58  71 …
6 24 50 96 154 225 …
x 24  x 96 154   x …

24  96 154 …
24 120 274 …
x 120  x  …

120 …
…..

    当然,发现前面O(n^3)的程序和这个Moessner数列的关联时我很是吃了一惊:在前面的程序里,如果你输出每一次i循环末所得到的数列,你会发现输出的这些数正好就是后面这个问题里被我们划掉的数,而它们其实就是第一类Stirling数!
    这到底是为什么呢?是什么东西把阶乘、第一类Stirling数、Moessner数列和那个O(n^3)的程序联系在一起的呢?昨天,我想这个问题想了一天,最后终于想通了。如果把Moessner数列排列成这个样子,一切就恍然大悟了:

  
    仔细观察上图,我们会发现:
    1. 按照Moessner数列的定义,每个数都应该等于它左边的数和左上角的数的和(这个“左边”可以跳过若干空格)。例如,35 = 9 + 26,46 = 11 + 35。排成一系列三角形后,每个三角形最右边一列的数就是被划去的数,它永远不能参与它下面的那些行的运算。
    2. 设a[n,i,j]表示左起第n个三角形阵列中的第i行右起第j列上的数,则a[n,i,j]=a[n-1,i-1,j]*n + a[n-1,i,j],例如274=50*5+24。如果递推时遇到空白位置而它左边隔若干空格的地方还有数的话,则需要用左边的数来补,例如18=4*4+2。对于每个三角形的最后一列来说,这个性质实际上就是第一类Stirling数的递推关系,因此Moessner数列中才会出现第一类Stirling数。
    3. 在第一类Stirling数中,s(n,1)=n! ,也即左起第n个三角形最底端的那个数等于n!。从上面的第二个性质来看,这也是显然的。
    4. O(n^3)的算法实际上就是在绘制上面这个图。每一次j循环末,我们得到
的序列是第i个三角形中每一行左起第j个数组成的序列。例如,计算第5个三角形内的数时,程序首先累加出1, 11, 46, 96, 120, 120,这样便算出了a[5]=120,数列的前5个数再次累加即得到1, 12, 58, 154, 274,由此算出a[4]=274。
    第二个性质可以利用第一个性质进行数学归纳法证明,证明很简单,我就不多说了。现在我尽可能少写一些繁琐的细节,节约一些时间用来复习古代汉语。

做人要厚道,
转贴请注明出处。

查看更多:
http://www.luschny.de/math/factorial/FastFactorialFunctions.htm
http://www.luschny.de/math/factorial/index.html <—- 巨牛,20多种阶乘算法的代码!

原创科普说明文:递归

    我的语文暑假作业之一,要求写任一说明文。
    本人菜鸟一个,文内漏洞百出,请踊跃提出,感谢不尽。
    Matrix67原创,转帖请注明出处。

递归


    公认的递归(Recursion)的标准定义是非常难理解的:若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的;若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。
    递归一词很少有过专业的定义,因此本文不在于去解释上一段文字的意义。虽然概念抽象,但递归其本身是不难理解的。通过本文的介绍,读者不一定能深入了解递归,只要能通过具体的例子模模糊糊地知道一些递归的思想和用途就可以了。
    究竟什么是递归呢?其实,递归就是大鱼吃小鱼,就是一条蛇咬住自己的尾巴。递归是指一样东西自己包含了自己。对于这一点,拿“谢尔品斯基地毯”(Sierpinski Gasket)来说明是最恰当不过的了。
    曲线在几何学中的概念很好理解,就是只有长度而没有宽度的线条。数学中有各种各样的曲线,如圆、直线、抛物线、双曲线、正弦曲线等。它们都可以用一定的方法画出来。例如,圆可以用圆规画出来,正弦曲线也可以用机器边在纸带上往复记号边拉纸带的方法画出来。事实上却没那么麻烦,画曲线有一个最常用的“万能方法”——似乎所有的曲线都可以用“描点法”画出,因为曲线没有宽度嘛,一个一个的点连起来,随便多奇怪的曲线都应该能画出。但随着数学的发展,这一点遭到了置疑。波兰数学家谢尔品斯基就想出了一个的确是一种曲线但永远无法画出的图形。他构造这种曲线的方法就运用了递归。
    随便找了一个正方形,把它分成3×3规格的相等的9个小正方形,然后把正中间的那一个挖掉。现在就只剩周围的八个小正方形了。接着重复这个过程,把8个小正方形的每一个都分成更小的9份,并挖掉它中间的那个。现在得到的就有8×8=64个正方形了。把这64个正方形继续这样划分,并且无限制的继续下去。这就是递归的思想,自己包含了自己,而后面的自己又包含了规模更小的自己。这样递归下去是没完的,因此最终得到的会是没有宽度的线条。这符合曲线的定义,但显然它是没办法画出来的。
    在现实生活中,递归的现象也是可以见到的。如果一台电视机的屏幕正显示着摄象机拍到的东西,那么把摄象机正对着这台电视机拍摄就会形成一个简单的递归。电视机显示着摄象机拍到的内容,而摄象机又对着电视机,这也就是说,摄象机将会拍摄到自己所拍到的东西。于是,递归形成了——在电视机上会显示出一层一层电视机的轮廓,即电视机里有电视机,层层循环下去永无止境。类似的例子也有一些,比如那个永远也讲不完的古老的故事,和Linda的第二张专辑的封面。
    递归通常是可以无限循环下去的。因此有这样一个笑话。作为一个狂热的电脑游戏迷,如果有一天你从一个完全陌生的地方醒来,你如何判断这是虚拟空间还是在现实中?答案是,找两面镜子来,互相对着放。如果出现周围的物体运动变慢等不正常的情况,说明你是在虚拟空间中。大自然是神奇的,它能处理两面镜子相对放置时镜子里应该显示的内容;但电脑就模拟不出来,如果电脑真遇到这种情况,指定会把CPU累死。
    但是,一旦给一个递归过程加上一个限制条件,让它递归到某一步时就停下来不要继续循环的话,递归将会派上大用场。
    我举一个最简单的例子。偶数就是能被2整除的数。我也可以用递归的方法定义偶数:一个偶数加上2还是偶数。这句话似乎足以说明了全部的数字,其实不然。因为如果没有任何限制,那么这个递归过程将是永无止境的,最终不会得到任何具体的答案。我们可以加上一个条件“0是偶数”。这样,情况就变了。比如,我们要看6是否为偶数,根据“一个偶数加上2还是偶数”,我们只需要看4是不是偶数。如果4是偶数,那么4+2也是偶数。而看4是否为偶数,又要看2是否为偶数,要看2是否为偶数,又要看0是否为偶数。本来这个递归应该是像这样无限地做下去的,但我们有了一个限制条件:我们已经知道了0是偶数。于是,2就是偶数了,4和6都是偶数了。同样的,我们就可以判断一切数字的奇偶性了。这就是利用递归来进行数学上的定义。
    这种定义方式有什么好处呢?一个简单的例子——
    很多人不明白为什么0的阶乘要规定成1,其实这用阶乘的递归定义一解释就清楚了。
    阶乘可以这样递归地定义:
        1)n的阶乘等于n-1的阶乘乘以n;
        2)1的阶乘是1;
    这样,所有自然数的阶乘就可以通过上面的两句话表示了。2的阶乘就是1×2;3的阶乘就是2的阶乘乘3,即1×2×3……不仅如此,我们还可以知道0的阶乘是多少:1的阶乘应该等于0的阶乘乘以1,显然0的阶乘必须是1才行。类似的,我们还能知道,负整数的阶乘没有意义。
    接下来,我将用两个经典的用递归的思想解决问题的例子来结束这篇文章。
    法国数学家艾得渥·卢卡斯(Edouard Lucas)于1883年在一份杂志上介绍了一个引人入胜的数学谜题——汉诺塔(Tower of Hanoi),并称这与古印度的一个传说有关。显然传说的具体内容已经不在本文论述的范围内了,但我想简单的介绍一下。
    相传印度有座大寺庙,它曾被认为是宇宙的中心。神庙中放置了一块上面插有三根长木钉的木板,在其中的一根木钉上,由上至下放了64片直径由小至大的圆环形金属片。古印度教的天神指示他的僧侣们将64片金属片全部移至另一根木钉上。移动规则只有两个:
        1.在每次的移动中,只能移动一片金属片;
        2.过程中任意时刻必须保证金属片小的在上,大的在下。
    直到有那么一天,僧侣们能将64片金属片按规则从指定的木钉上全部移至另一根木钉上,那么,世界末日即随之来到,世间的一切终将被毁灭,万物都将至极乐世界。
    这个传说经常被认为是卢卡斯教授为推广这个数学谜题而捏造的,但不管怎么说,卢卡斯是成功了。这玩意儿变成了家喻户晓的益智游戏之一,后来又成为了学习递归的必修课程。
    对汉诺塔问题的研究焦点集中在如何以最少的步骤完成全部金属片的转移这一问题上。解决这个问题的方法运用了递归的思想。
    我们可以这样想。64片金属片太多了,我们似乎能简化一下。假如我们已经知道了如何移动63片,我们就可以把这63片看成一个整体。那么这64片的移动过程就出来了:第一步,移动前63片到另一根木钉上;第二步,移动
第64片到第三根木钉上;第三步,把那63片移回第64片上面。看到了吗?问题已经解决了,因为这形成了递归。我们可以继续对移动63片的方法进行研究:把前62片移开,移动第63片,移回前62片。继续研究62片金属的移动方法……这样下去,一直推到如何移动2片金属。而移动2片金属的方法是非常简单的,已经不需要继续讨论了,于是,全部问题到此解决。
    发现递归思想的实质了吗?这让我想起了一个笑话。笑话的主人公是一个反应迟钝,只具有数学思维的数学家;为了使这个笑话更形象,我们就把这个人暂且定为童明国(注:我们数学老师的名字)。
    童明国去做消防队员。队长问:“如果你这里起火了,你怎么办?”童明国答:“用消火栓。”
    “那么如果这里没有起火呢?”
    “很简单,先把这里点燃,然后这个问题就转化为了一个我已经解决的问题了。”
    我要举的下一个例子与这个有异曲同工之处。
    小学奥赛接触过一个叫作“报30”的游戏,就是从1开始,两人轮流报数,每个人都只能报接下来的一个数或两个数。比如,第一个人可以报1,也可以报1、 2;如果第一个人报1、2,第二个人就可以报3和3、4;然后第一个人又报;这样报下去,最先报到30的人获胜。
    这个游戏非常没意思,因为它有必胜策略。
    最先报到30的人获胜,很显然,先报到27的人一定可以胜;那么,先报到24的人就一定能胜了……递归下去,21,18,最终得到的结论就是,先报到3的人一定必胜。也就是说,后报者必胜。不管先报者报多少,后报者始终报到3的倍数,这样定能获胜。
    这个游戏有很多变种,但换汤不换药,万变不离其宗。比如,把规则改成“最先报到30的人就输”。这样,先报到29的人就赢了,然后同样递归,26,23,20……
    前几天在网上看到了这个游戏的一个较难的变种。
    有10枚硬币,每人轮流取硬币,可以拿一枚、两枚或四枚;取到最后一枚硬币者胜。
    这样还有必胜策略吗?答案是肯定的,而且同样可以运用递归的思想来解决。
    如果硬币的总数只有一枚,则先取者赢;
    如果硬币的总数是两枚,则先取者赢;
    如果硬币的总数是三枚,则先取者输;
    如果硬币的总数是四枚,则先取者赢;
    如果硬币的总数是五枚,则先取者赢(取两枚,对方面临三枚的情形,必输);
    如果硬币的总数是六枚,则先取者输(不管取多少,对方面临的情形都是必胜的情形);
    如果硬币的总数是七枚,则先取者赢(取一枚,对方面临六枚的情形,必输);
    如果硬币的总数是八枚,则先取者赢(取两枚,对方面临六枚的情形,必输);
    如果硬币的总数是九枚,则先取者输(不管取多少,对方面临的情形都是必胜的情形);
    如果硬币的总数是十枚,则先取者赢(取一枚,对方面临九枚的情形,必输)。

(本文参考资料:本文  呵呵)