一、概述
这个问题困扰许久,最后解决时也发现超出了规定的执行时间。
现发表我的解答总结一下这个过程中的想法。
二、问题描述
要求计算出N!(N的阶乘)中最后出现的非0数字。
三、我的思路
如图1.1所示,我将问题的解决分为了五个部分,以N=125时来进行介绍。
大致思路是:将阶乘中出现乘数中会导致出现末尾为0的部分单独处理(10的倍数和以5结尾的数),统计出找到的5的个数,并且在剩下的乘数中删掉相应个数的2,最后留下的数则可以简单的利用相乘取余来得到答案。
在这过程中还发现一个规律:每一行去除了5的倍数和10的倍数后,剩余的8个数相乘的结果总是为6,这意味着很多行可以不用进行相乘取余的运算。
第一部分:10的倍数(红色方框)
假设用于求出最后非0数字的算法为f(n),根据图1.1可以知道,10的倍数的乘数的最后非0数字计算可以用f(n/10)表示。
第二部分:5的倍数(蓝色方框)
如表1.1所示,表中统计出了50个以5结尾的数除去因子5以后,结果的分布规律。
表1.1 前50个以5结尾的数字中除去因子5后的结果分布规律
5 | 1 | 55 | 1 | 105 | 1 | 155 | 1 | 205 | 1 | 255 | 1 | 305 | 1 | 355 | 1 | 405 | 1 | 455 | 1 | |
15 | 3 | 65 | 3 | 115 | 3 | 165 | 3 | 215 | 3 | 265 | 3 | 315 | 3 | 365 | 3 | 415 | 3 | 465 | 3 | |
25 | 1 | 75 | 3 | 125 | 1 | 175 | 7 | 225 | 9 | 275 | 1 | 325 | 3 | 375 | 3 | 425 | 7 | 475 | 9 | |
35 | 7 | 85 | 7 | 135 | 7 | 185 | 7 | 235 | 7 | 285 | 7 | 335 | 7 | 385 | 7 | 435 | 7 | 485 | 7 | |
45 | 9 | 95 | 9 | 145 | 9 | 195 | 9 | 245 | 9 | 295 | 9 | 345 | 9 | 395 | 9 | 445 | 9 | 495 | 9 |
为什么要列上述表呢?
先以5个一组看,可以发现每个组中有区别的部分是带有颜色标记(红或蓝)的数字部分。并且每5个一组中,颜色标记的数字分布规律是:1,3,n,7,9(n为1,3,7,9其中一个)。这说明了每5个以5结尾的数字相乘的结果必然为:1x3xnx7x9=9xn
再以25个一组看,可以发现,两个组中有区别的部分只有蓝色标记的数字部分。并且每25个一组中,蓝色标记的数字分布规律也是:1,3,n,7,9。这说明了每25个以5结尾的数字相乘的结果必然为:(9x1)x(9x3)x(9xn)x(9x7)x(9x9)=1xn
经过统计总结,125(5x5x5),725(5x5x5x5)...等以5^m个以5结尾的数字为一组也具有同样的规律。
并且在统计时发现,n的结果具有规律:当在最顶层的5^m一组中,m必然是1,3,1,7,9。此外下面的层中,n取决于上一层的组的个数。
例如:
计算125!,含有13个以5结尾的数(5,15,25......125),对于13,不够25个数,所以最顶层是一组是5^1级别,有两个组,那么这两个组的结果可以计算为9x1,9x3,并且还剩下3个5^0级别的数字,它们同样满足1,3,n,7,9的分布规律,这里的n则要通过5^1级别的组数来确定,因为有两个5^1级别的组,所以这里的n是1(1,3,1,7,9中的第3个数字),则剩下的3个数的计算结果为1x3x1。
图1.1 125!中各计算部分
第三部分:找出与5匹配的2(绿色方框)
这部分利用前阶段中找到的5的个数,从头开始找2的倍数,并收集其除以2后的结果。
第四部分:“不完整的行”部分:(棕色方框)
这部分用于计算第三部分中一行中未被计算完的数字以及最后一行的数字相乘的结果。
第五部分:“完整的行”部分(图1.1中只被红、蓝方框穿过的行)
统计发现,删掉了10的倍数和以5结尾的数字的一行中,剩下的8个数字相乘结果总是6,因为6^n=6(n>0)所以这部分可以用6来表示结果。
四、总结
测试了10万个数据,结果和网上AC的解答结果一致,故初步认定算法的正确性是没问题的。
但是很遗憾,超过了规定的执行时间。
网上AC的解答十分巧妙,但是事后感觉只有自己先有了一部分思考,才能更好的吸收别人高超的想法。
五、源代码
#include <iostream>
#define MAX 100000
struct Div5
{
unsigned res;
unsigned rem;
};
Div5 div5[MAX];
unsigned idx5[5]={1,3,1,7,9};
unsigned getCore(unsigned idx,unsigned p)
{
idx++;
unsigned tmp=div5[idx].rem;
while(tmp==2)
{
if(idx==p-1)
return idx5[div5[idx].res];
tmp=div5[++idx].rem;
}
return idx5[tmp];
}
unsigned func(unsigned n)
{
if(n==0)return 1;
//part 10 -- 10n
unsigned part10=func(n/10);
//sum of 5
unsigned m=0;
unsigned sum5=0;
unsigned tmp;
for(unsigned i=5;i<=n;i+=10)
{
m++;
tmp=i;
while(tmp%5==0)
{
sum5++;
tmp/=5;
}
}
//part 5 -- 5n
unsigned part5=1;
unsigned tmp2;
unsigned p=0;
while(m>=5)
{
div5[p].res=m/5;
div5[p].rem=m%5;
m/=5;
p++;
}
unsigned core,base,root;
if(p>0)
{
root=p%2==0?1:9;
base=div5[p-1].res;
core=1;
switch(base)
{
case 1:part5*=root;break;
case 2:part5*=3;break;
case 3:part5*=3*root;break;
default:break;
}
if(root==1)root=9;
else root=1;
base=div5[p-1].rem;
core=idx5[div5[p-1].res];
int i=p-1;
while(i>=0)
{
switch(base)
{
case 1:part5*=root;break;
case 2:part5*=3;break;
case 3:part5*=3*core*root;break;
case 4:part5*=core;break;
default:break;
}
part5%=10;
i--;
if(i>=0)
{
base=div5[i].rem;
core=base>=3?getCore(i,p):1;
if(root==1)root=9;
else root=1;
}
}
}
else
{
base=m;
switch(base)
{
case 2:
case 3:part5*=3;break;
default:break;
}
}
//part res -- eat 5
unsigned partRes=1;
unsigned idx;
for(idx=2;sum5>0;idx++)
{
if(idx%5!=0)
{
tmp=idx;
while(tmp%2==0 && sum5>0)
{
sum5--;
tmp/=2;
}
tmp%=10;
partRes*=tmp;
partRes%=10;
}
}
//part res
for(tmp=idx%10;tmp<=9&&tmp<=n;tmp++)
if(tmp%5!=0)
partRes*=tmp;
partRes%=10;
if(n>10)
{
for(tmp=2,tmp2=n%10;tmp<=tmp2;tmp++)
if(tmp%5!=0)
partRes*=tmp;
partRes%=10;
}
//part 6 -- 6n
int tmp3=n/10*10-idx;
unsigned part6=tmp3>10?6:1;
return (part5*part10*part6*partRes)%10;
}
int main()
{
using namespace std;
unsigned n;
while(cin>>n)
cout<<func(n)<<endl;
return 0;
}