POJ-1150(求排列数P(n,m)中最后一个非0的数字)

 

1.如何求出n阶乘中质因数x(比如说5)出现的次数?

  1. int get(int m, int n)//计算n!中质因子m的出现次数  
  2. {  
  3.     if (n == 0) return 0;  
  4.     return (n / m) + get(m, n / m);  

解析:

求n!中m出现的次数。那么n可以分解为


即为求q的值。

例如:假设n=10,m=3;  n! = 1*2*3*4*5*6*7*8*9*10那么出现质因数3的次数为4次。

有公式:


[n/m]表示不大于n的数中m的倍数贡献一个m[n/] 表示不大于n的数中的倍数再贡献一个m

例如:

n=25,m=3; 

[n/m] = 8  代表:1乘到25中出现了8次3的倍数,他们分别是:3,6,9,12,15,18,21,24。  

[n/] = 2  代表:1乘到25中出现了2次的倍数,他们分别是:9,18。

由于之前9和18在3的倍数中已经贡献了一个3出去了。现在在的倍数中再次出现的时候,只需要再贡献一个3(也是最后一个3)出去即可。如果有一个数是m的5次方。那么在这个数会在[n/]的时候贡献出最后一个3。

 

2.如何求出n!阶乘最后非0位?
    比如说我们要找10!最后非0位,由于质因数2和5组合之后会使得末尾产生0.那么我们不妨把10!中2,5质因数全部去掉,(但是请注意2的数目其实比5的要多,所以我们要在最后考虑多余的2对末位的影响)
    如 1*2*3*4*5*6*7*8*9*10 去掉2 ,5 因子后 就是1*1*3*1*1*3*7*1*9*1,由于25因子已经被去除,那么剩下的数字末尾一定是3791中四者之一。然后我们再求出这么一串数相乘以后末尾的数是几.最后再补上2对末位的影响即可!

3.总结一下,求10!最后一个非0位的步骤如下:
step1:首先将10!中所有2,5因子去掉;
step2:然后求出剩下的一串数字相乘后末尾的那个数。
step3:由于去掉的2比5多,最后还要考虑多余的那部分2对结果的影响。
step4:output your answer!

这里面步骤2是个难点。如何求出剩下的这串数字相乘后最后一位是几呢?这可以转化成求出这些数里面末尾是3,7,9的数字出现的次数,然后依次算出3,7,9出现次数次方的尾数。比如,3出现了4次,则3出现的次数产生的尾数就是1。7出现了2次,7出现的次数产生的尾数就是4。这样,最后把3,7,9出现的次数统计出来,然后依次求出这三个数出现次数的次方后的尾数,最后尾数相乘,即得步骤2的结果。注意,因为这些数(3,7,9)的n次方的尾数是有规律的,周期为4,不信你可以推一下。
      好,现在问题就是如何求出这串数字中末尾379各自出现的次数了;

一个数列实际上可以分成偶数列和奇数列,以1*2*3*4*5*6*7*8*9*10为例,分成

1 3 5 7 9,   2 4 6 8 10

这样我们尝试分别进行统计,可以发现,实际上2,4,6,8,10中的个数也就是1 2 3 4 5中的个数,也就是说我们又把这个问题划分成了一个原来问题的子问题。


g(n)表示奇数列中的数目,所以我们需要解决g(n)

 

再次观察g(n)

实际上又分成了两部分1 3 7 9 11 13 17 19 21…… 以及5的奇倍数5,15,25…… 说明又出现了子问题,如果要统计这个数列中末尾为x(1,3,7,9)的个数可以这样写:

 

n/10表示有多少组(1,3,7,9)。例如n=36时,n/10=3,表示有3(1,3,7,9),他们是(1,3,7,9)(11,13,17,19)(21,23,27,29)

(n%10 >= x)表示n这个数的个位是否大于x,若是则应表达式为true,遇到前面的加号,自动提升为数值型1。例如,n=36,x=3时,(n%10 >=x) = (6 >= 3) = true

g(n/5,x)表示5的奇倍数51525……的子问题。

 

 

 

这样利用了两个递归方程,我们就可以在lgn的时间内计算出末尾为1,3,7,9的数的个数了

好了,现在我们得到了这串数字中末尾是3,7,9的数字的个数,我们利用循环节的性质可以快速地算出这串数字相乘后mod 10的结果,在考虑下当时多除的2(其实也可以用循环节来处理),便可求出答案!


    解决了上面两个子问题,我想求P(n,m)最后一个非0位就变得十分容易了。
P(n,m)实际上等于 n! / (n-m)!
      我们可以求出n! 和(n-m)!中质因数2,5,3,7,9分别出现的次数,然后再各自相减。然后再用循环节处理,即可!
      BTW,这里还要注意一个trick,就是2的出现次数如果小于5,(这对于排列数来说是可能的)我们可以直接输出5,如果2的数目等于5,那么2的循环节不需要考虑。至于3,7,9的循环节,由于这些数的4次方末位刚好是1,所以就不需要特殊考虑了。

 

 

 

 

 

 

代码:

#include <stdio.h>

 

int get(int m, int n)//计算n!中质因子m的出现次数

{

       if (n == 0) return 0;

       return (n / m) + get(m, n / m);

}

int g(int x, int n)//计算f(1) to f(n) 中,奇数数列中末尾为x的数出现的次数

{

       if (n == 0) return 0;

       return (n / 10) +(n % 10 >= x)+ g(x, n / 5);

}

int getx(int x, int n) //计算f(1) to f(n)中,末尾为x的数的出现次数

{

       if (n == 0) return 0;

       return getx(x, n / 2) + g(x, n);

}

循环节

第一行表示出现的次数相乘后产生的尾数

第一行表示出现的次数相乘后产生的尾数

第一行表示出现的次数相乘后产生的尾数

第一行表示出现的次数相乘后产生的尾数

针对2而言:第一列表示出现^N次(即^N)产生的尾数(6),

           第二列表示出现^N次(即^N)产生的尾数(2),

           第三列表示出现^N次(即^N)产生的尾数(4),

           第四列表示出现^N次(即^N)产生的尾数(8),

针对3而言(7,9情况同):

           第一列表示出现^N次(即^N)产生的尾数(1),或者表示出现了次产生的尾数(1),不影响结果

           第二列表示出现^N次(即^N)产生的尾数(3),

           第三列表示出现^N次(即^N)产生的尾数(9),

           第四列表示出现^N次(即^N)产生的尾数(7),

 

 
// 循环节

int table[4][4] = {

       6, 2, 4, 8,

       1, 3, 9, 7,

       1, 7, 9, 3,

       1, 9, 1, 9

};

 

 

 

 

 

 

 

int main()

{

       int a, b;

       while (scanf("%d%d", &a,&b) == 2) {

              int res = 1;

              int diff_number2 =get(2, a)- get(2, a- b);

              int diff_number5 =get(5, a)- get(5, a- b);

              int diff_number3 =getx(3, a)- getx(3, a- b);

              int diff_number7 =getx(7, a)- getx(7, a- b);

              int diff_number9 =getx(9, a)- getx(9, a- b);

             

              if (diff_number5> diff_number2) {

                     printf("5\n");

                     continue;

              } else {

                     //依次算出,3,7,9中每一个数字出现的次数相乘后产生的尾数,然后四个数字相乘得最后的尾数。

 

                     //必须判断有没有多余的,因为循环节中没有考虑出现个的情况

                     if (diff_number2!= diff_number5) {

                            res *= table[0][(diff_number2 - diff_number5)% 4];

                            res %= 10;

                     }

 

                     res *= table[1][diff_number3 % 4];

                     res %= 10;

                     res *= table[2][diff_number7 % 4];

                     res %= 10;

                     res *= table[3][diff_number9 % 4];

                     res %= 10;

                     printf("%d\n",res);

              }

       }

       return 0;

}

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值