杭电1066

题目描述:

The expression N!, read as "N factorial," denotes the product of the first N positive integers, where N is nonnegative. So, for example, 
N N! 
0 1 
1 1 
2 2 
3 6 
4 24 
5 120 
10 3628800 

For this problem, you are to write a program that can compute the last non-zero digit of the factorial for N. For example, if your program is asked to compute the last nonzero digit of 5!, your program should produce "2" because 5! = 120, and 2 is the last nonzero digit of 120. 

首先是最简单的办法,代码:

#include<stdio.h>
int main(){
	int n;
	while(scanf("%d",&n)!=EOF){
		int i,s=1;
		for(i=1;i<=n;i++){
			s=s*i;
			while(s%10==0) s=s/10;
			s=s%10;
		}
		printf("%d\n",s);
	}
}
自然而然的,超时了,然后写了一个比较复杂的算法,结果是WA,在网上搜索了一番,发现是个变态题,N最大可以是10^100,引用大神的方法:

首先考虑某一个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!,阶乘的最后一个非零数字必为偶数(显然,因为在N!的质因数里2的个数要多),所以有一个很特别的除法规律: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即 
可。整个算法相当巧妙,写起来也比较轻松。

AC代码:

#include<stdio.h> 
#include<string.h> 

int t[2]={6,4}; 
int a[10]={1,1,2,6,4,2,2,4,2,8}; 
#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(){
	int i,n,h,out,tens;
	char s[MAXN];
	while(gets(s)){
		n=strlen(s);
		out=1;
		tens=h=0;
		for(i=0;i<n;i++)
			s[i]^=48;
		while(h<n){
			//求所有尾数非5,0的连乘积的尾数,再除以4
			out*=a[s[n-1]];
			out%=10;
			//累加整10的个数,因为每10个尾数循环1周
			if(n-h>1) tens+=s[n-2];
			//除以5,使尾数为5、0的数转为非5、0并计算之
			for(i=n-1;i>h;i--)
				s[i]=(s[i]<<1)/10+(s[i-1]<<1)%10;
			if(!(s[h]=(s[h]<<1)/10)) h++;
		}
		if(tens) out=(out*t[tens%2])%10;
		printf("%d\n",out);
	}
	return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值