题目传送门:http://poj.org/problem?id=1150
http://poj.org/problem?id=3406
1150的题意是给你一个nPm, 求这个数从低位开始找第一个非零的数字。
#include <iostream>
#include <cstdio>
using namespace std;
int table[4] = {6, 2, 4, 8};
int step[10] = {0,0,1,-1,2,-1,0,1,-1,2};
const int mod = 4;
int getstep(int x)
{
int s = 0;
while (x) {
s = (s + x / 5) % mod;
if (x % 5 == 2) s = (s + 1) % mod;
if (x % 5 == 4) s = (s + 2) % mod;
x /= 5;
}
return s;
}
int main()
{
int n,m;
while (~scanf("%d%d",&n,&m)) {
if (n <= 1 || m == 0) {
printf("1\n");
} else if (m == 1) {
printf("%d\n",n);
} else {
printf("%d\n",table[(getstep(n) + 4 - getstep(n - m)) % mod]);
}
}
return 0;
}
暴力一定超时,从网上找了一篇blog,真的很牛。
http://www.tuicool.com/articles/naaUZ3
大致的意思是每对当前的中间过程的得数 乘一个数,会对最后一位施加一个影响,对于>1的数字的阶乘,其最后一位一定是2 4 6 8中的一个,不妨令table[] = {6, 2, 4, 8}(2的4次方,1次方,2次方,3次方),而每乘一个数就是对当前最后非零位的一次改变,对应在table这个表中就体现为左移,右移或者不动,这个规律也很好找,试一下就好了,就是代码中的step。
由于排列数P(n,m) = n!/(n - m)! 所以计算出n!移动的步数,在计算出(n-m)!移动的步数,就可以得到结果了。
#include <cstdio>
#include <iostream>
using namespace std;
int n,m;
int table[4][4] = {
6, 2, 4, 8,
1, 3, 9, 7,
1, 7, 9, 3,
1, 9, 1, 9,
};
int getprime(int n, int x)
{
int ret = 0;
while (n) {
ret += n / x;
n /= x;
}
return ret;
}
int getodd(int n, int x)
{
if (n == 0) return 0;
return getodd(n / 5, x) + (n % 10 >= x) + (n / 10);
}
int getend(int n, int x)
{
if (n == 0) return 0;
return getend(n / 2, x) + getodd(n, x);
}
int main()
{
while (~scanf("%d%d",&n,&m)) {
m = n - m;
int two = getprime(n,2) - getprime(m,2);
int five = getprime(n,5) - getprime(m,5);
int three = getend(n,3) - getend(m,3);
int seven = getend(n,7) - getend(m,7);
int nine = getend(n,9) - getend(m,9);
int ans = 1;
if (five > two) {
puts("5");
continue;
}
ans = ans * table[1][three % 4] % 10;
ans = ans * table[2][seven % 4] % 10;
ans = ans * table[3][nine % 4] % 10;
if (two > five) {
ans = ans * table[0][(two - five) % 4] % 10;
}
printf("%d\n",ans);
}
}
这种方法是比较通用的方法,通过计算n!的因子数来确定最后一位。首先要先把所有因子为10的全部去掉,这样后面就不会有0了,但10不是质数,并不容易找出,不妨 找出全部因子为2和因子为5的个数,这样就可以找到因子为10的个数了。剔除2和5之后,剩下的数字一定是末尾为3 7 9的数字的积,而这些数字自身的乘方末尾数字是有循环节的,最后不要忘了把多余的因子2补上,而2的乘方末位也是循环的。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m;
int table[4][4] = {
6,2,4,8,
1,3,9,7,
1,7,9,3,
1,9,1,9,
};
int getprime( int n, int x)
{
int ret = 0;
while (n) {
ret += n / x;
n /= x;
}
return ret;
}
int getodd (int n, int x)
{
if (n == 0) {
return 0;
} else {
return (n / 10) + (n % 10 >= x) + getodd(n / 5, x);
}
}
int getend (int n, int x)
{
if (n == 0) return 0;
return getend(n / 2, x) + getodd(n, x);
}
int main()
{
while (~scanf("%d%d",&n,&m)) {
int two = getprime(n, 2) - getprime(m, 2) - getprime(n - m, 2);
int five = getprime(n, 5) - getprime(m, 5) - getprime(n - m, 5);
if (five > two) {
puts("5");
continue;
}
int three = getend(n, 3) - getend(m, 3) - getend(n - m, 3);
int seven = getend(n, 7) - getend(m, 7) - getend(n - m, 7);
int nine = getend(n, 9) - getend(m, 9) - getend(n - m, 9);
three += 2 * nine; nine = 0;
int ans = 1;
ans *= table[1][three % 4];
ans *= table[2][seven % 4];
ans *= table[3][nine % 4];
if (two > five) {
ans *= table[0][(two - five) % 4];
}
printf("%d\n",ans % 10);
}
return 0;
}
而对于组合数,第一种方法就不适用了,因为末位数字不一定就是table中的数字了,所以使用了第二种方法,不过要注意的是,组合数中分母写成阶乘形式,末位为3的个数可能会比分子上的多,所以我们把9拆开,拆成两个3.