现在的位置: 首页 > 综合 > 正文

hdu1066题讲解

2013年10月21日 ⁄ 综合 ⁄ 共 3372字 ⁄ 字号 评论关闭

 

/*1066:为了把0去掉,我们把所有的因数2和5都提出来,放到最后再处理。N!中的N个相乘的数可以分成两堆:奇数和偶
数。偶数相乘可以写成(2^M)*(M!),M=N DIV 2。M!可以递归处理,因此现在只需讨论奇数相乘。考虑1*3*5*7*9
*11*13*15*17*...*N(如果N为偶数则是N-1),这里面是5的倍数的有5,15,25,35,...,可以其中的5提出来,变
成(5^P)*(1*3*5*7*9* ...),后面括号中共P项,P=(N DIV 5+1) DIV 2,而后面的括号又可以继续提5出来,递归
处理.现在剩下的数是1*3*7*9*11*13*17*19*....这些数我们只需要他们的个位数,因为(1*3*9*11*13* ...)MOD
 10=(1*3*7*9*11*13*...)MOD 10.我们列出余数表,1 3 1 9 9 7 9 1 1 3 1 9 9 7 9 ……。我们发现每八项MOD

 10的结果是一个循环.算出奇数的结果后,我们再回头看统计了多少个2和5需要乘入。把2和5配对完都是N!后面
 的0,看剩下的2有几个。如果有剩下的2,考虑2^N的个位数又是2 4 8 6 2 4 8 6 ……每四项一个循环,找出这
 个个位数后,和前面的结果相乘,再取个位数就是答案。由于我们完全把2和5的因素另外处理,所以在所有的乘
 法中,都只需要计算个位数乘法,并且只保留个位数的结果。

但让我很惊异的是:为什么我提交总是WA?后来我才知道,原因是这道题的N相当大!达到了10^100!要用大数来
处理!GPC四项编译开关全关的,自然查不出来!而且上面这个算法换成大数后会很麻烦。还有什么别的好方法吗?

答案是有的。我们设F(N)表示N!的尾数。

先考虑简单的。考虑某一个N!(N < 10),我们先将所有5的倍数提出来,用1代替原来5的倍数的位置。由于5的倍
数全被提走了,所以这样就不会出现尾数0了。我们先把0..9的阶乘的尾数列出来(注意,5的倍数的位置上是1)
,可以得到table[0..9]=(1, 1, 2, 6, 4, 4, 4, 8, 4, 6)。对于N < 5,直接输出table[N]即可;对于N>=5,由
于提出了一个5,因此需要一个2与之配成10,即将尾数除以2。注意到除了0!和1!,阶乘的最后一个非零数字必为
偶数,所以有一个很特别的除法规律:2/2=6,4/2=2,6/2=8,8/2=4。比较特殊的就是2/2=12/2=6,6/2=16/2=8.这样
我们就可以得到如下式子:

      table[N]

F(N)= ---------- (0 <= N < 10)
      2^([N/5])

再考虑复杂的。考虑某一个N!(N>=10),我们先将所有5的倍数提出来,用1代替原来5的倍数的位置。由于5的倍数
全被提走了,所以这样就不会出现尾数0了。我们观察一下剩下的数的乘积的尾数,通过table表,我们发现这10
个数的乘积的尾数是6,6*6的尾数还是6,因此我们将剩下的数每10个分成一组,则剩下的数的乘积的尾数只与
最后一组的情况有关,即与N的最后一位数字有关。(因为根据最后一位就能知道它在尾数表中处于第几个位置,
只要将尾数表之前的数相乘就得到最后一组的尾数。)由于我们把5的倍数提出来了,N!中一次可以提出[N/5]
个5的倍数,有多少个5,就需要有多少个2与之配成10,所以有多少个5,最后就要除以多少个2。注意到除2的
结果变化是4个一循环,因此如果有A个5,只需要除(A MOD 4)次2就可以了。A MOD 4只与A的最后两位数有关,
很好求算。剩下的5的倍数,由于5已经全部处理掉了,就变成[N/5]!。于是,我们可以得到
一个递归关系:

      F([N/5]) * table[N的尾数] * 6
F(N) = ----------------------------------- (N > 10)
          2^([N/5] MOD 4)

这样我们就得到了一个O(log5(N))的算法,整除5可以用高精度加法做,乘2再除10即可。整个算法相当巧妙,
写起来也比较轻松。
*/
/*#include <string.h>
#include<stdio.h>
#define MAXN 10000
int lastdigit(char* buf)
{   
 const int mod[20]={1,1,2,6,4,2,2,4,2,8,4,4,8,4,6,8,8,6,8,2};   
 int len=strlen(buf),a[MAXN],i,c,ret=1;   
 if (len==1) return mod[buf[0]-'0'];   
 for (i=0;i<len;i++) a[i]=buf[len-1-i]-'0';  
 for (;len;len-=!a[len-1]){  
 ret=ret*mod[a[1]%2*10+a[0]]%5;  
 for (c=0,i=len-1;i>=0;i--)  
 c=c*10+a[i],a[i]=c/5,c%=5;  
 }  
 return ret+ret%2*5;  
 }  
 int main()  
 {  
 char a[1000]; 
    while(scanf("%s",&a)!=EOF) 
 { 
 printf("%d\n",lastdigit(a)); 
 } 
 return 0;
 }
*/

/*#include <stdio.h>
#include <string.h>
#include <math.h>
int main()
{
 int n,i,j;
    while(scanf("%d",&n)!=EOF)
    {
        int k=0,a;
        if(n==0)
        {
            printf("1\n");
            continue;
        }
        int s[100000];
        memset(s,0,100000);
        s[0]=1;
        for(i=1;i<=n;i++)
  {
             a=0;
            for(j=0;j<=k;j++)
   {
               s[j]=s[j]*i+a;
               a=s[j]/10000;
               s[j]=s[j]%10000;
   }
              if(a)
              s[++k]=a;
  }
           printf("%d",s[k]);
               for(i=k-1;i>=0;i--)
                  printf("%04d",s[i]);
           printf("\n");
 }
    return 0;
}*/

/*#include <stdio.h>
#include <string.h>
#include <math.h>
int main()
{
 int n,i,j;
    while(scanf("%d",&n)!=EOF)
    {
        int k=0,a;
        if(n==0)
        {
            printf("1\n");
            continue;
        }
        int s[100000];
        memset(s,0,100000);
        s[0]=1;
        for(i=1;i<=n;i++)
  {
             a=0;
            for(j=0;j<=k;j++)
   {
               s[j]=s[j]*i+a;
               a=s[j]/10000;
               s[j]=s[j]%10000;
   }
              if(a)
              s[++k]=a;
  }
           printf("%d",s[k]);
               for(i=k-1;i>=0;i--)
                  printf("%04d",s[i]);
           printf("\n");
 }
    return 0;
}
*/

抱歉!评论已关闭.