bzoj 3552: 最右非零的数 && hduoj 1066: Last non-zero Digit in N!(求N!的最后一个非0位)

3552: 最右非零的数

Time Limit: 1 Sec   Memory Limit: 128 MB
Submit: 89   Solved: 27
[ Submit][ Status][ Discuss]

Description

        给出正整数 N (可能有前导 0 ),请求出 N! 最右非零的数位的值。

Input

        第一行一个数 T 表示数据组数
        下接 T 行每行一个数 N 表示一组数据

Output

        对于每组数据,输出一行一个数表示这组数据的答案

Sample Input

2
5
4

Sample Output

2
4

思路与公式:

1:n!的尾部的"0"全部来自因子5和因子2(一对5和2产生一个0),如果把这些因子去掉,则可符合要求(2的个数

明显于5的个数)

2:设F(n)为答案所要求的数,G(n)为1,2…n中将5的倍数的数换成1后的各项乘积,G(15)=1*2*3*4*1*6*7*8*9*

1*11*12*13*14*1(G(n)%10必不为0),则可推出以下两个公式

① n! = (n/5)! * 5^(n/5) * G(n)  ② F(n!) = F((n/5)!) * F[5^(n/5) * G(n)] (可以递归)

3:根据②可知F[5^(n/5) * G(n)] = F[G(n)/(2^(n/5))],其中G(n)/(2^(n/5))可找到规律

4:枚举可找到上述规律为Mp[20] = {1,1,2,6,4,2,2,4,2,8,4,4,8,4,6,8,8,6,8,2}(20一循环)这样就可以算出答案啦

PS:

n<=19  ----  G[n] = data[n]

n>=20  ----  G[n] = data[n%20]


n过大,这里用字符串处理

#include
#include
char str[10005];
int a[10005], Mp[20] = {1,1,2,6,4,2,2,4,2,8,4,4,8,4,6,8,8,6,8,2};  /*这个是F[5^(n/5)*G(n)]的前20(0-19)项,后面循环*/
int main(void)
{
	int T, len, ret, i, c;
	scanf("%d", &T);
	while(T--)
	{
		scanf("%s", str);
		len = strlen(str);
		for(i=len-1;i>=0;i--)
			a[len-1-i] = str[i]-'0';     /*将n的每一位存入数组a[],其中a[0]是最低位,a[len-1]是最高位*/
		ret = 1;
		while(len!=0)  /*用循环代替递归*/
		{
			c = 0;
			ret = ret*Mp[a[1]%2*10+a[0]]%10;   /*"a[1]%2*10+a[0]"是计算n%20的值*/
			for(i=len-1;i>=0;i--)
			{
				c = c*10+a[i];    /*计算n除以5之后的值,并将其存入(覆盖)a[]*/
				a[i] = c/5;
				c %= 5;
			}
			if(a[len-1]==0)     /*如果最高位是0,去掉最高位*/
				len--;
		}
		if(strcmp(str, "1")==0 || strcmp(str, "0")==0)
			printf("1\n");
		else
			printf("%d\n", ret);
	}
	return 0;
}

你可能感兴趣的:(数学or几何)