poj 1151

原题:

The Last Non-zero Digit
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 3104 Accepted: 826

Description

In this problem you will be given two decimal integer number N, M. You will have to find the last non-zero digit of the NP M.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 NP M. For example, if NP M 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
    这是一道数论好题。之所以这么说是因为它真正考察了ACMer的数学思维能力和观察
能力,并且此题不再是随便一个数学公式就能水过(比如poj 1401 ,裸的欧几里德公式
即可,poj 1423 斯特林公式)。因此这道题足足卡了我好久。我首先是用线性筛素数法,
想用欧几里德公式水过,但该法实际上只使用于<=10^6的数据量,否则很容易tle或re.
后来优化n次无果之后找了一下解题报告,现在贡献一篇好的。
http://www.cppblog.com/abilitytao/archive/2009/10/31/99907.html
其大致思想先是给出解题的步骤:
step1:首先将10!中所有2,5因子去掉;
step2:然后求出剩下的一串数字相乘后末尾的那个数。
step3:由于去掉的2比5多,最后还要考虑多余的那部分2对结果的影响。
step4:output your answer!
重点是突破第二步,以下是摘抄原文了:

一个数列实际上可以分成偶数列和奇数列,以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中的个数,也就是说我们又把这个问题划分成了一个原来问题的子问题。

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

再次观察g(n)

实际上又分成了两部分1 3 7 9 11 13 17 19 21。。。以及5的奇倍数5,15,25。。。

说明又出现了子问题,如果要统计这个数列中末尾为x(1,3,7,9)的个数可以这样写:g(n,x) = n/10+(n%10 >= x)+g(n/5,x)

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

好了,现在我们得到了这串数字中末尾是3,7,9的数字的个数,我们利用循环节的性质可以快速地算出这串数字相乘后mod 10的结果,

在考虑下当时多除的2(其实也可以用循环节来处理),便可求出答案!

 
以下就是我的代码了,第二步那我根据某 大牛的代码进行了简化,用到了位操作:
#include "stdio.h"
#include "string.h"
int num[20];
int n,m;
int form[4][4]={6,2,4,8,1,3,9,7,1,7,9,3,1,9,1,9};
int getnum(int n,int val) //求2或5的个数
{
int ans=0;
int p=val;
while(n/val)
{
   ans+=n/val;
   val*=p;
}
return ans;
}
int getx(int n,int val)   //求末尾为val的数字的个数
{  
    int ans=0;
    for(int i=n;i;i>>=1)  
    for(int j=i;j;j/=5)  
    {  
       ans+=j/10+(j%10>=val);  
    }  
    return ans;  
}
int main()
{
while(scanf("%d%d",&n,&m)==2)
{
   int ans=1;
   m=n-m;
   num[2]=getnum(n,2)-getnum(m,2);
   num[5]=getnum(n,5)-getnum(m,5);
   if(num[5]>num[2])
   {
    printf("5/n");
    continue;
   }
   else if(num[5]==num[2]) ans=1;
   else ans=form[0][(num[2]-num[5])%4];
   num[3]=getx(n,3)-getx(m,3);
   ans=ans*form[1][num[3]%4]%10;
   num[7]=getx(n,7)-getx(m,7);
   ans=ans*form[2][num[7]%4]%10;
   num[9]=getx(n,9)-getx(m,9);
   ans=ans*form[3][num[9]%2]%10;
   printf("%d/n",ans);
}
return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值