The Last Non-zero Digit POJ - 1150(n!mod p)

题意:

要求你求出 n ! ( n − m ) ! ) \frac{n!}{(n-m)!)} (nm)!)n!中最后一个非0的数字.

题目:

In this problem you will be given two decimal integer numberN,M. You will have to find the last non-zero digit of the N P M ^{N}P_{M} NPM.This means no of permutations of N things taking M at a time.

Input

The input contains several lines of input. Each line of the input file contains two integers N (0 <= N<= 20000000), M (0 <= M <= N).

Output

For each line of the input you should output a single digit, which is the last non-zero digit of NPM. For example, if N P M ^{N}P_{M} NPM is 720 then the last non-zero digit is 2. So in this case your output should be 2.

Sample Input

10 10
10 5
25 6

Sample Output

8
4
2

分析:

说实话这道题上来我就没看懂题意,这怎么就 N P M ^{N}P_{M} NPM== n ! ( n − m ) ! ) \frac{n!}{(n-m)!)} (nm)!)n!了?In a word ,我感觉到了不友好。在这里插入图片描述
然后我就开始了啃书环节,具体在《挑战程序设计》P293,之后恶意铺面而来,花费我一晚上,终于摸透了这个题,必须滴好好说道说道。
(1)首先是若求n!的最后一位,我们可以将所有的因数10去掉,问题就转换为了求这个数的最后一位。根据以往的做题经验,这时候只要找到所有的因子2和5,放着对最后特判对于最后一位的影响就好了,所以正常while循环暴力找因子数,然后超时了,代码如下:

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;

const int N=2e7+10;
int a[5][N];
int b[5][4]={{6,2,4,8},{5,5,5,5},{1,3,9,7},{1,7,9,3},{1,9,1,9}};
int get(int x){
    if(x==2) return 0;
    if(x==5) return 1;
    if(x==3) return 2;
    if(x==7) return 3;
    if(x==9) return 4;
}
void init(){
    for(int i=2; i<N; ++i){
        int t=i;
        int su=0,sm=0;
        while(t%2==0) t/=2,++su;
        while(t%5==0) t/=5,++sm;
        a[get(2)][i]=a[get(2)][i-1]+su;
        a[get(5)][i]=a[get(5)][i-1]+sm;
        a[get(3)][i]=a[get(3)][i-1];
        a[get(7)][i]=a[get(7)][i-1];
        a[get(9)][i]=a[get(9)][i-1];
        if(t%10 && t%10!=1) ++a[get(t%10)][i];
    }
}

int main()
{
    init();
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        int su=a[get(2)][n]-a[get(2)][n-m];
        int sm=a[get(5)][n]-a[get(5)][n-m];
        if(su<sm) printf("5\n");
        else {
            //printf("+++++++ %d %d\n",su,sm);
            su-=sm;
            int d3=a[get(3)][n]-a[get(3)][n-m];
            int d7=a[get(7)][n]-a[get(7)][n-m];
            int d9=a[get(9)][n]-a[get(9)][n-m];
            int tmp=b[get(3)][d3%4]*b[get(7)][d7%4]*b[get(9)][d9%4];
            //printf("+++++++++ %d %d\n",su,tmp);
            tmp*=su?b[get(2)][su%4]:1;
            int ans=tmp%10;
            printf("%d\n",ans);
        }
    }
    return 0;
}

这并不冤枉,其实开数组2e7就该知道有问题,试了编译器,可以运行,就硬着头皮写下来了,不出意料,果真超时了。那这里就用到了“白书”的定理,我懒得敲了。
在这里插入图片描述
具体代码如下:

int sum(int n,int p){//计算n!中质因子m的出现次数
    return n==0?0:n/p+sum(n/p,p);
}

(2)当我们对(1~n)去除因子2,5后发现最后一位的值,只可能是 1,3, 7,9这四个数,因为最后一位若为1,n!相乘对最后一位值的变化没有影响,所以可以不用考虑,只考虑 3,7 ,9,即可。这时发现了规律,例如,即当存在多个3时,只考虑个位值,出现了循环节{1,3,9,7},注意第一位为整除时,所以为值为1。你以为到这就算完了,还不够!
在这里插入图片描述

(3)如上超时代码,不能打表开数组存,所以每次输入就直接调用函数,直接对n!进行讨论,将其分为奇偶两个部分:
【1】对于偶数序列,我们只需将它除 2 即可递归转化为奇数序列(其实就是消去因子2)。
【2】对于奇数序列,我们可以发现,每 10个数字中就有 3 , 7 , 9 各一个,但又因为(1~n)中有 5的倍数,所以继续除 5,递归消去因子。
(4)这里就差不多了,但还是要注意:
一,当因子2的个数小于5的个数时,由于此时末尾必为奇数(1,3,7,9中一个),所以相乘最后一位必为5,直接输出。
二,当因子2的个数大于5的个数时,需要考虑因子2对结果的一个影响,这时也有与前面一样的规律{6,2,4,8},理解了之后让我们欢乐敲代码吧;

AC模板:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int n,m;
int e[4][4]={{6,2,4,8},{1,3,9,7},{1,7,9,3},{1,9,1,9}};
int sum(int n,int p){//计算n!中质因子m的出现次数,用到“挑战程序设计”P293推论
    return n==0?0:n/p+sum(n/p,p);
}
int odd(int n,int p){//奇数数列中末尾为x的数出现的次数,消去1~n数中存在的因子5
    return n==0?0:n/10+(n%10>=p)+odd(n/5,p);
}
int even(int n,int p){//末尾为x的数的出现次数,消去1~n数中存在的因子2;
    return n==0?0:even(n/2,p)+odd(n,p);
}
int main()
{
    while(~scanf("%d%d",&n,&m)){
        int su=sum(n,2)-sum(n-m,2);
        int sm=sum(n,5)-sum(n-m,5);
        int a=even(n,3)-even(n-m,3);
        int b=even(n,7)-even(n-m,7);
        int c=even(n,9)-even(n-m,9);
        if(su<sm){
             printf("5\n");
             continue;
        }
        int ans=e[1][a%4]*e[2][b%4]*e[3][c%4]%10;
        if(su!=sm)
        ans*=e[0][(su-sm)%4];
        printf("%d\n",ans%10);
    }
    return 0;
}

备战ccpc分站赛ing ,题目分析简略,见谅,转载请注明出处。。。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值