POJ-1150(求排列数P(n,m)中最后一个非0的数字)

 

1    如何求出n阶乘中质因数x(比如说5)出现的次数?

int get(int m, int n)//计算n!中质因子m的出现次数
{
	if (n == 0) return 0;
	return (n / m) + get(m, n / m);
}

 2    如何求出n!阶乘最后非0位?
比如说我们要找10!最后非0位,由于质因数2和5组合之后会使得末尾产生0.那么我们不妨把10!中2,5质因数全部去掉,(但是请注意2的数目其实比5的要多,所以我们要在最后考虑多余的2对末位的影响)
如 1*2*3*4*5*6*7*8*9*10 去掉2 ,5 因子后 就是1*1*3*1*1*3*7*1*9*1,由于2,5因子已经被去除,那么剩下的数字末尾一定是3,7,9,1中四者之一。然后我们再求出这么一串数相乘以后末尾的数是几.最后再补上2对末位的影响即可!

总结一下,求10!最后一个非0位的步骤如下:
step1:首先将10!中所有2,5因子去掉;
step2:然后求出剩下的一串数字相乘后末尾的那个数。
step3:由于去掉的2比5多,最后还要考虑多余的那部分2对结果的影响。
step4:output your answer!

这里面步骤2是个难点。如何求出剩下的这串数字相乘后最后一位是几呢?这可以转化成求出这些数里面末尾是3,7,9的数字出现的次数(为啥?因为这些数的n次方是有规律的,周期为4,不信你可以推一下)
好,现在问题就是如何求出这串数字中末尾3,7,9各自出现的次数了;

一个数列实际上可以分成偶数列和奇数列,以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(其实也可以用循环节来处理),便可求出答案!

解决了上面两个子问题,我想求P(n,m)最后一个非0位就变得十分容易了。
P(n,m)实际上等于 n! / (n-m)!
我们可以求出n! 和(n-m)!中质因数2,5,3,7,9分别出现的次数,然后再各自相减。
然后再用循环节处理,即可!
BTW,这里还要注意一个trick,就是2的出现次数如果小于5,(这对于排列数来说是可能的)我们可以直接输出5,如果2的数目等于5,那么2的循环节不需要考虑。至于3,7,9的循环节,由于这些数的4次方末位刚好是1,所以就不需要特殊考虑了。

int get(int m, int n)//计算n!中质因子m的出现次数
{
	if (n == 0) return 0;
	return (n / m) + get(m, n / m);
}
int g(int x, int n)//计算f(1) to f(n) 中,奇数数列中末尾为x的数出现的次数
{
	if (n == 0) return 0;
	return (n / 10) + (n % 10 >= x) + g(x, n / 5);
}
int getx(int x, int n) //计算f(1) to f(n)中,末尾为x的数的出现次数
{
	if (n == 0) return 0;
	return getx(x, n / 2) + g(x, n);
}
int table[4][4] = {//循环节
	6, 2, 4, 8,
	1, 3, 9, 7,
	1, 7, 9, 3,
	1, 9, 1, 9
};
int main()
{
	int a, b;
	while (scanf("%d%d", &a, &b) == 2) {
		int res = 1;
		int diff_number2 = get(2, a) - get(2, a - b);
		int diff_number5 = get(5, a) - get(5, a - b);
		int diff_number3 = getx(3, a) - getx(3, a - b);
		int diff_number7 = getx(7, a) - getx(7, a - b);
		int diff_number9 = getx(9, a) - getx(9, a - b);
		if (diff_number5 > diff_number2) {
			printf("5\n");
			continue;
		} else {
			if (diff_number2 != diff_number5) {
				res *= table[0][(diff_number2 - diff_number5) % 4];
				res %= 10;
			}
			res *= table[1][diff_number3 % 4];
			res %= 10;
			res *= table[2][diff_number7 % 4];
			res %= 10;
			res *= table[3][diff_number9 % 4];
			res %= 10;
			printf("%d\n", res);
		}
	}
	return 0;
}



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值