• 第2章 数字之魅——数字中的技巧2.2


    2.2关于阶乘的一点知识

    问题1:求 N!末尾有多少个0。

    问题2:N!中二进制表示中最低位1的位置。

    首先对于问题1:

        对于N!的末尾有多少个0这个问题。要追溯到算术基本定理:

    算术基本定理:

    任何一个大于1的自然数N,都可以唯一分解成有限个质数的乘积 N=(P_1^a1)*(P_2^a2)......(P_n^an) , 这里P_1<P_2<...<P_n是质数,其诸方幂 ai 是正整数。
     
    以上是百度百科对于该定理的描述。由此我们可以把一个数看做是由有限个素数因子构成的。那么N!也不例外。设N!=M。
    M = S*10^n = S*(2*5)*(2*5)*(2*5)...2*5有n队。S尾部是不含0的。所以原问题就转化为寻找N!中有多少对2和5的因子了。
    也就是说寻找2和5的个数中哪个最少。取最少的那个(原先描述最少用最小,果断容易引起误解。)。对于这个问题。还有一步可以通过思维化简问题的就是2和5的因子到底哪个更少呢?明显是5的。所以我们要寻找数中5的因子的个数。
     
    问题描述:寻找 N!中5的因子的个数。
    法1:从数的角度
    很常规的解法,将数除以5判断是否能被5除尽。可以的话就计数。循环执行。
    int count;
    int num = N!;
    count = 0;
    while(num)
    {
        if(num%5==0)
        {
            count ++;
        }
        num /= 5;
    }
    法1
    观察这个式子会发现类比求二进制中1的个数这个问题很像。对于这个问题,我们可以考虑一下。2的因子的个数其实就是最后一个1后面的0的个数。
    那么我们是否可以用所谓的五进制呢?要将一个数转成一个五进制数本身就耗费太多时间了。但是这是个好想法。学会类比,这是很重要的。
    同时这个角度有很大的缺陷,N!往往是很大的。所以我们经常是无法表达出来。
     
    法2:从组合数的角度(结构)
    组合数:N!= 1*2*3*4*5*...*N。
    观察会发现一个很大的数构成一些小数乘积的结构。其中有一些数有一个5的因子,比如5,10。其中有一些数有2个5的因子。比如25,125。其中有一些数字有3个,比如125。那么按常规的。我们先找有多少个至少有1个5的因子的数。也就是说找1~N中有多少个能被5整除的数。
    直接想并不容易。而用逆向思维地想。这些数一定是5*n。凡是5的倍数的就一定能被5整除。(废话)
    有1个的时候。N取值 [5,10)就是5.而[10,15)则是2个。那么可以发现就是 [N/5]  "[ ]"代表取整。
    之后我们再数有2个5的因子的数。再数有3个的。4个的。直到没有。也就是[N/pow(5,k)]==0。
    并且根据指数爆炸。pow(5,k)增长是十分快的。算法时间只需要log级别。而对于pow(5,k)我们可以考虑使用快速幂。
    另外这种计数原理实质上是容斥定理。关于容斥定理。摘自百度百科。
     
    容斥定理:
     
    计数时,必须注意无一重复,无一遗漏。为了使重叠部分不被重复计算,人们研究出一种新的计数方法,这种方法的基本思想是:先不考虑重叠的情况,把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复,这种计数的方法称为容斥原理
    int count;
    count = 0;
    void Count(int N)
    {
        temp = (N>>i);
        while(temp)
        {
            count += temp;
            temp >>= 1;
        }
    }
    法2

    反思:我们要学会类比。逆向思维的思考。(其实这个可以有利于DP的状态转移方程的思考)

    然后对于问题2:

        求N!的最低位的1的位置。最低位的1的位置不过就是1后面0的个数。那么对于0的个数问题其实可以转化为该数含有多少个2的因子的问题。通过问题1中所述的类比。很简单就可知。所以问题转化为求N!的2的因子的个数问题。这个问题完全可以用上述的方法2来处理。而编程之美上又给出了一个规律。 即1的位置 = N - N中1的个数。

    推理过程:

    一个二进制的数101001。

    根据求法1,可以获得 10100+1010+101+10+1

             = 11111+111+1

             = 100000-1+1000-1+1-1

               = 10101-3.

    观察这个等式  10100+1010+101+10+1= 11111+111+1 对于11111 是由101001这个数最左边的1向右边移动而获得的。

    类比的。111是由101001由左往的1 构成。而1就是由101001最右边的1构成的。

    而 11111正好是比原来的100000少1 所以就可以简单发现这个规律的恒成立。其中包含了。二进制的一些性质在内。

    这个规律明显是由法1得来的所以只对阶乘数有效。

    在这里就不附上问题2的代码了。

  • 相关阅读:
    HTML5 表单新增属性
    js中获取css属性
    Java 枚举(enum)【感觉不是很常用】
    Java数组
    Java的反射机制
    IO,NIO【重点掌握】,Socket,Channel等的网络编程
    多线程
    注解
    动态代理
    动态编译
  • 原文地址:https://www.cnblogs.com/Milkor/p/4348319.html
Copyright © 2020-2023  润新知