POJ 1150 The Last Non-zero Digit

题意

求排列数 NPM 最后一个非零位上的数字是多少。NPM即为 P(N,M) = N!/(N-M)!


解题分析
很明显,这是道数论题,直接计算是行不通的。对于n!,我们可以写成质因子相乘的形式。P(N,M)为两个数的阶乘相除,所以原问题化解为求 n!最后一个非零位上的数字是多少这个子问题。
我们并不关心最终结果末尾的零位,而最后的零位都是由因子2与5相乘而得,所以先去除所有的因子2、5 。(当然会存在2、5的个数不相同的情况,先不管,最后再来处理)
以 10! = 1*2*3*4*5*6*7*8*9*10 为例, 去除因子2、5后得到 1*1*3*1*1*3*7*1*9*1,剩下的数字末尾必定为1、3、7、9之中的某一个。将这些剩下的数字相乘,模上10得到ans',然后再考虑因子2、5个数大小关系:
a.  count(2) == count(5), 答案即为ans';
b.  count(2) > count(5),答案为(ans'  * 2^(count(2) - count(5)) )%10 ;
c.  count(2) < count(5),答案为(ans'  * 2^(count(5) - count(2)) )%10 ;
求10!过程总结为:
a.  去除10!中所有因子2、5;
b.  求余下所有数相乘后得到的数的个位数字;
c.  考虑因子2、5对最后结果的影响;
d.  Good Job!That's your answer!



Problem 1: 怎么求步骤b?

Re:因子 2、3、5、7、9的n次幂的个位其实是有循环节的,你可以去验证一下:

int table[4][4] =
{
    {6,2,4,8},//2^n%10的循环节,2的个数为0时,需特殊处理。
    {1,3,9,7},//3
    {1,7,9,3},//7
    {1,9,1,9},//9
};


Proble 2: 怎么扩展到求 n! ?
Re: n! = 1*2*3*4*5*...*n 这个序列可以分为奇数列 1 3 5 7 9 11 ...  与偶数列 2 4 6 8 10 12 ...   而偶数列可以化为奇数列,偶数的个数为 n/2。在奇数列中可以分为1 3 7 9 11 13 17 29... 与 5 15 25...(5的奇数倍)。在奇数列中这样来求尾数为 3 7 9 的个数(尾数为1 的数可以直接忽略,对结果无影响):

int get_odd(int n, int x) {     // 计算n!中奇数序列中尾数为x的数出现次数(x为3、7、9)
    if (n == 0) {
        return 0;
    }
    return n/10 + (n%10 >= x) + get_odd(n/5, x);
}
即不考虑n个位数时,每10个数中有一个x, 如果n个位数为x,则再加上1( (n%10 >= x)),然后加上5的奇数倍序列中除去因子5后尾数为x的数的个数。

再给出因子2、5的求法:

int get_2(int n) {              // 计算n!中 因子2 出现次数
    if (n == 0) {
        return 0;
    }
    return n/2 + get_2(n/2);
}
int get_5(int n) {              // 计算n!中 因子5 出现次数
    if (n == 0) {
        return 0;
    }
    return n/5 + get_5(n/5);
}



最后利用循环节的性质,将这些2、3、5、7、9以幂的形式乘起来,就还原了 n!,乘的过程中只需保留最后一位数就OK了。不过在因子5的个数大于 因子2的个数情况下,可以直接输出5,因为任何奇数相乘得到的仍是奇数,而奇数乘5的结果个位必然是5!


题解代码:

// by weikd
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

int get_2(int n) {              // 计算n!中 因子2 出现次数
    if (n == 0) {
        return 0;
    }
    return n/2 + get_2(n/2);
}

int get_5(int n) {              // 计算n!中 因子5 出现次数
    if (n == 0) {
        return 0;
    }
    return n/5 + get_5(n/5);
}

int get_odd(int n, int x) {     // 计算n!中奇数序列中尾数为x的数出现次数(x为3、7、9)
    if (n == 0) {
        return 0;
    }
    return n/10 + (n%10 >= x) + get_odd(n/5, x);
}

int get_x(int n, int x) {       // 计算n!中尾数为x的数出现次数(x为3、7、9)
    if (n == 0) {
        return 0;
    }
    return get_x(n/2, x) + get_odd(n, x);
}

int table[4][4] =
{
    {6,2,4,8},//2^n%10的循环节,2的个数为0时,需特殊处理。
    {1,3,9,7},//3
    {1,7,9,3},//7
    {1,9,1,9},//9
};

int main() {
    int n, m;
    while (scanf("%d%d", &n, &m) != EOF) {
      int num_2 = get_2(n) - get_2(n-m);
      int num_5 = get_5(n) - get_5(n-m);
      int num_3 = get_x(n, 3) - get_x(n-m, 3);
      int num_7 = get_x(n, 7) - get_x(n-m, 7);
      int num_9 = get_x(n, 9) - get_x(n-m, 9);
      int ans = 1;
      if (num_5 > num_2) {
         printf("5\n");
         continue;
      }
      else {
         if (num_5 != num_2) {
            ans *= table[0][(num_2-num_5)%4];
         }
         ans %= 10;
         ans *= table[1][num_3%4];
         ans %= 10;
         ans *= table[2][num_7%4];
         ans %= 10;
         ans *= table[3][num_9%4];
         ans %= 10;
      }
      printf("%d\n", ans);
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值