洛谷 P2529 [SHOI2001]击鼓传花 解题报告

P2529 [SHOI2001]击鼓传花

题意:求出\(n!\)末尾最后一位非0数字

数据范围:\(n<=10^{100}\)


我们从简单的开始考虑

1.显然,\(n!\)可以被这么表示

\(n!=c \times 2^a \times 5^b\)

显然有\(a>b\)

2.末尾的元素即为\(c \%10 \times 2^{a-b} \%10\)

显然这个复杂度是我们所不能接受的

我们发现\(5\)很特殊

我们把所有\(5\)的倍数都取出来(注意取出的是\(5\)的倍数而不是因数\(5\)),给每个\(5\)配一个\(2\)

相当于把\(5*1,5*2,5*3,...,5*n\)中的\(5\)约去,发现剩下的是一个规模1/5的相同子问题

\(fac(i)\)表示\(i!\)的末尾非0数字,则有\(fac(i)=fac(\lfloor i/5 \rfloor) \times ? \% 10\)

我们想办法求出\(?\)的贡献

因为因子\(2\)消不完,所以末尾必须是偶数

发现剩下的数可以直接\(mod\) \(10\)

把每\(20\)位分成一块,对末位的贡献为\(6\)

因为$6 \times $任意偶数,末尾不变

所以答案只需要把20位以内的额外贡献找到就可以

我们考虑直接把这20位打表,然后递归处理子问题

复杂度可以接受(高精度我不会算)


Code:

#include <cstdio>
#include <cstring>
const int N=102;
struct l_num
{
    int num[N];
    l_num()
    {
        memset(num,0,sizeof(num));
    }
    l_num(char c[])
    {
        memset(num,0,sizeof(num));
        num[0]=strlen(c);
        for(int i=1;i<=num[0];i++)
            num[i]=c[num[0]-i]-'0';
    }
    l_num friend operator /(l_num n1,int n2)
    {
        for(int i=n1.num[0];i>1;i--)
        {
            n1.num[i-1]+=n1.num[i]%n2*10;
            n1.num[i]/=n2;
        }
        n1.num[1]/=n2;
        if(!n1.num[n1.num[0]]) --n1.num[0];
        return n1;
    }
    int friend operator %(l_num n1,int n2)
    {
        return n1.num[0]==1?n1.num[1]:(n1.num[2]&1)*10+n1.num[1];
    }
};
int init[21]={1,1,2,6,4,2,2,4,2,8,4,4,8,4,6,8,8,6,8,2,6};
char c[103];
int cal(l_num fac)
{
    return fac.num[0]?init[fac%20]*cal(fac/5)%10:1;
}
int main()
{
    int t=5;
    while(t--)
    {
        scanf("%s",c);
        l_num fac(c);
        printf("%d\n",cal(fac));
    }
    return 0;
}


2018.8.9

posted @ 2018-08-09 17:16  露迭月  阅读(354)  评论(0编辑  收藏  举报