zoukankan      html  css  js  c++  java
  • [CSP-S模拟测试]:数字(数学+高精度)

    题目描述

    很简单,给出正整数$n$,求出$n!$在十进制表示下的从最末非零位开始的总共$k$位。


    输入格式

    第一行一个正整数$T$,表示有$T$组数据
    接下来$T$行,每行两个正整数$n$和$k$。


    输出格式

    每组测试数据,按照高位到低位的顺序输出,共$k$位,包含前导$0$(若高位不足,用前导$0$补足)。


    样例

    样例输入:

    3
    1 1
    5 1
    10 2

    样例输出:

    1
    2
    88


    数据范围与提示

    对于$20\%$的数据,$nleqslant 100,k=1,2,3$分别占$10\%,5\%,5\%$
    对于另外$30\%$的数据,$nleqslant 10^7,k=1,2,3$分别占$10\%,10\%,10\%$
    对于另外$30\%$的数据,$nleqslant 10^{18},k=1,2,3$分别占$10\%,10\%,10\%$
    对于另外$20\%$的数据,$nleqslant 10^{100},k=1,2,3$分别占$5\%,5\%,10\%$
    $Tleqslant 100$


    题解

    一道$200$多行的数学题……

    讲一下推到过程(其实主要是学长的……)

    为方便,不妨做如下定义:

    $aperp b$表示$a$与$b$互质,即$gcd(a,b)=1$。

    $a|b$表示$a$可以整除$b$,即$gcd(a,b)=a$。

    $a equire{cancel}cancel{-}b$表示$a$无法整除$b$,即$gcd(a,b) eq a$。

    $a\%b$表示满足$aequiv(mod b)$的最小自然数$x$。

    设$Fp(k,x)$为$x$中因子$k$的最大幂次,即满足$x=rk^n(n,rin N)$的最大$n$。

    设$Ext(k,x)$为$x$除去所有因子$k$后的值,即$Ext(k,x)=frac{x}{k^{Fp(k,x)}}$。

    定义完了之后,我们接着看着道题,其实我们就是要求$Ext(10,n!)mod 10^k$的值。

    不妨先来考虑$k=1$。

    $ecause 10^k=10=2 imes 5$,且$2perp 5$,且$2$和$5$都是质数(哇塞,性质好多~)。

    求一个值对一个合数取模的值,而这个合数还是两个质数的积,所以考虑中国剩余定理($CRT$),用其求出上式对$2$和$5$取模的值。

    $Ext(10,n!)mod 2$的值很容易求,简单说一下怎么求。

    $ecause$在$n>1$的时候有$Fp(2,n!)>Fp(5,n!)$,又$ecause 10=2 imes 5$,$ herefore Fp(10,n!)=Fp(5,n!)$。

    $$egin{array}{ll} herefore Ext(10,n!) &=& dfrac{n!}{10^{Fp(10,n!)}} \ &=& dfrac{n!}{2^{Fp(10,n!)}5^{Fp(10,n)}} \ &=& dfrac{n!}{2^{Fp(5,n!)}5^{Fp(5,n!)}}end{array}$$

    $ herefore$分子$n!$中因子$2$的幂次大于分母上$2$的幂次,所以有:

    $$Ext(10,n!)equiv 0(mod 2)(n>1)$$

    那么我们就只要求$Ext(10,n!)mod 5$的值就好了。

    刚刚我们知道我们要求$dfrac{n!}{2^{Fp(5,n!)}5^{Fp(5,n!)}}$,而分母中$s^{Fp(5,n!)}perp 5$,$ herefore$其在$mod 5$的意义下是有逆元的。

    那么我们考虑如何快速求解$Fp(5,n!)$,由定义可得,$n!=prod limits_{k=1}^n k$,$ herefore$可以求出$[1,n]$中有因子$5$的数的个数$sim$有因子$5^k$的数的个数,每一个$k$都能产生新的贡献,$ herefore$只要求和即为$Fp(5,n!)$,也就是说$Fp(5,n!)=sum limits_kleftlfloorfrac{n}{5^k} ight floor$,这样做的时间复杂度是$Theta(log_5 n)$的。

    这部分的贡献我们先不着急,剩下的问题就等价于求$Ext(5,n!)$了。

    但是显然我们还是不好求$Ext(5,n!)$,然而我们发现$Ext(k,x)$在$k$一定时是完全积性函数,即$Ext(k,a) imes Ext(k,b)=Ext(k,ab)$,给出简单证明:

    $$egin{array}{ll} Ext(k,ab) &=& dfrac{ab}{k^{Fp(k,ab)}} \ &=& dfrac{ab}{k^{Fp(k,a)}k^{Fp(k,b)}} \ &=& Ext(k,a) imes Ext(k,b)end{array}$$

    $ herefore$有:

    $$Ext(5,n!)=prod limits_{k=1}^kExt(5,k)$$

    又$ecause Ext(k,k)=1$,$ herefore$根据上面的性质,还可以得到:

    $$Ext(k,x)=Ext(k,frac{x}{k}),k|x$$

    利用上面的性质,继续推导:

    $$egin{array}{ll} Ext(5,n!) &=& prod limits_{k=1}^nExt(5,k) \ &=& prod limits_{kin [1,n],5|k}Ext(5,frac{k}{5}) imes prod limits_{kin [1,n],5 equire{cancel}cancel{-}k}Ext(5,k) \ &=& Ext(5,(frac{5}{n})!) imes prod limits_{kin [1,n],5 equire{cancel}cancel{-}k}Ext(5,k) \ &=& Ext(5,(frac{n}{5})!) imes prod limits_{kin [1,n],5 equire{cancel}cancel{-}k}k end{array}$$

    其中对于$5|k$是原问题的子问题,可以用递归求解。

    而对于后面$5 equire{cancel}cancel{-}k$的部分,由于我们要求的只是上式对$5$取模后的值,所以我们可以将$k$全部对$5$取模后分组,于是有:

    $$prod limits_{kin [1,n],5 equire{cancel}cancel{-}k}kequiv(prod limits_{kin [1,5],5 equire{cancel}cancel{-}k}k)^{leftlfloorfrac{n}{5} ight floor} imes prod limits_{kin [1,nmod 5],5 equire{cancel}cancel{-}k}k(mod 5)$$

    前半部分是定值的若干次幂,直接快速幂求解即可,但是$leftlfloorfrac{n}{5} ight floor$我们需要降幂,可以考虑扩展欧拉定理,即对于任意$a,n,p eq 0$,有$a^nequiv a^{nmod phi(p)+phi(p)}(mod p)$。

    后半部分预处理出来即可。

    最后我们找回来我们一开始咕着的$2^{Fp(5,n!)}$的逆元,将上式乘上它在用$CRT$即可。

    这样,我们对于$k=1$的求解就结束了~

    下面考虑一般情况……

    对于$k=1$的情况,我们考虑的是模数是$10$的情况,而对于$k=3$的情况,我们只需要想办法将其拓展到模数是$1000$就好了。

    发现$1000=10^3=2^3 imes 5^3$,$ herefore$我们可以用$2^3=8$和$5^3=125$作为$CRT$的剩余器即可。

    $ecause$发现$Fp(2,n!)$依然大于$Fp(5,n!)$,$ herefore$可以暂且将$Ext(10,n!)mod 8$当作$0$来处理(只有$n$很小的时候才不能被$8$整除,但是直接暴力求解就好了,无非就是打一个数据点分治),那么现在的瓶颈就在于$mod 125$意义下值的求解。

    那么我们现在就是想办法拓展下式:

    $$Ext(5,(frac{n}{5})!) imes prod limits_{kin [1,n],5 equire{cancel}cancel{-}k}k$$

    改变分组策略即可,即将组长改为$125$即可,于是上式变成了:

    $$Ext(5,n!)equiv Ext(5,(frac{n}{5})!) imes (prod limits_{kin [1,125],5 equire{cancel}cancel{-}k}k)^{leftlfloorfrac{n}{125} ight floor} imes prod limits_{kin [1,nmod 125],5 equire{cancel}cancel{-}k}k(mod 125)$$

    也可以这么看,设剩余器$p=5^k$,则有:

    $$Ext(5,n!)equiv Ext(5,(frac{n}{5})!) imes (prod limits_{kin [1,p],5 equire{cancel}cancel{-}k}k)^{leftlfloorfrac{n}{p} ight floor} imes prod limits_{kin [1,nmod p],5 equire{cancel}cancel{-}k}k(mod p)$$

    问题解决了,剩下的就看代码实现了,我一开始打了$242$行才$AC$的,可是对面$DeepinC$就打了$40$行,反正我是做不到了,能$A$就行……

    时间复杂度:$Theta(log_5^n)$。

    期望得分:$100$分。

    实际得分:$100$分。


    代码时刻

    #include<bits/stdc++.h>
    using namespace std;
    char ch[101];
    int N[101],EXT[101],FLAG[101],NOW[101],wzc[101];
    long long n;
    int k;
    int mod[]={1,10,100,1000};
    int fiv[]={1,5,25,125};
    int phi[]={1,4,20,100};
    long long mzz[200],fac[200];
    long long ans;
    long long qpow(long long x,long long y,long long MOD)
    {
    	long long res=1;
    	while(y)
    	{
    		if(y&1)res=res*x%MOD;
    		x=x*x%MOD;
    		y>>=1;
    	}
    	return res;
    }
    void pre_work()
    {
    	fac[0]=1;
    	memset(EXT,0,sizeof(EXT));
    }
    void work()
    {
    	ans=1;
    	for(long long i=1;i<=n;i++)
    	{
    		ans*=i;
    		while(!(ans%10))ans/=10;
    		ans%=mod[k];
    	}
    }
    void copy(){for(int i=0;i<=N[0];i++)FLAG[i]=N[i];}
    void divideFLAG()
    {
    	int jw=0;
    	for(int i=FLAG[0];i;i--)
    	{
    		jw*=10;
    		jw+=FLAG[i];
    		wzc[i]=jw/5;
    		jw%=5;
    	}
    	int len=0;
    	int top=FLAG[0];
    	for(int i=top;i;i--)
    	{
    		if(wzc[i])len=max(len,i);
    		FLAG[i]=wzc[i];
    	}
    	FLAG[0]=len;
    }
    void explus()
    {
    	int jw=0;EXT[0]=max(EXT[0],FLAG[0]);
    	for(int i=1;i<=EXT[0];i++)
    	{
    		EXT[i]+=FLAG[i]+jw;
    		jw=EXT[i]/10;
    		EXT[i]%=10;
    	}
    	for(int i=EXT[0]+1;i;i++)
    	{
    		if(!jw)break;
    		EXT[i]++;
    		jw=EXT[i]/10;
    		EXT[i]%=10;
    		EXT[0]=i;
    	}
    }
    int ModEXT()
    {
    	int res=0;
    	for(int i=EXT[0];i;i--)
    	{
    		res=res*10+EXT[i];
    		res%=phi[k];
    	}
    	return res;
    }
    void get()
    {
    	int now=0;
    	memset(wzc,0,sizeof(wzc));
    	for(int i=N[0];i;i--)
    	{
    		now=now*10+N[i];
    		if(now<fiv[k])continue;
    		int pre=now;
    		now/=fiv[k];
    		now%=10;
    		wzc[i]=now;
    		now=pre-now*fiv[k];
    	}
    	int len=0;
    	int top=N[0];
    	for(int i=top;i;i--)
    	{
    		if(wzc[i])len=max(len,i);
    		NOW[i]=wzc[i];
    	}
    	NOW[0]=len;
    }
    int ModNOW()
    {
    	int res=0;
    	for(int i=NOW[0];i;i--)
    	{
    		res=res*10+NOW[i];
    		res%=phi[k];
    	}
    	return res;
    }
    int ModN()
    {
    	int res=0;
    	for(int i=N[0];i;i--)
    	{
    		res=res*10+N[i];
    		res%=fiv[k];
    	}
    	return res;
    }
    void divideN()
    {
    	int jw=0;
    	for(int i=N[0];i;i--)
    	{
    		jw*=10;
    		jw+=N[i];
    		wzc[i]=jw/5;
    		jw%=5;
    	}
    	int len=0;
    	int top=N[0];
    	for(int i=top;i;i--)
    	{
    		if(wzc[i])len=max(len,i);
    		N[i]=wzc[i];
    	}
    	N[0]=len;
    }
    void ex_work()
    {
    	reverse(N+1,N+N[0]+1);
    	EXT[0]=0;
    	copy();
    	while(FLAG[0])
    	{
    		divideFLAG();
    		explus();
    	}
    	long long inv=qpow(qpow(2,phi[k]-1,fiv[k]),ModEXT(),fiv[k]);
    	for(int i=1;i<mod[k];i++)
    		if(!(i%(mod[k]/fiv[k])))
    			mzz[i%fiv[k]]=i;
    	for(int i=1;i<fiv[k];i++)
    	{
    		fac[i]=fac[i-1];
    		if(i%5)fac[i]=fac[i]*i%fiv[k];
    	}
    	long long res=1;
    	while(N[0])
    	{
    		get();
    		res=res*qpow(fac[fiv[k]-1],ModNOW(),fiv[k])%fiv[k];
    		res=res*fac[ModN()]%fiv[k];
    		divideN();
    	}
    	res=res*inv%fiv[k];
    	ans=mzz[res];
    }
    int main()
    {
    	int T;scanf("%d",&T);
    	while(T--)
    	{
    		pre_work();
    		scanf("%s%d",ch+1,&k);
    		N[0]=strlen(ch+1);
    		for(int i=1;i<=N[0];i++)N[i]=ch[i]-'0';
    		if(N[0]==1){n=N[1];work();}
    		else ex_work();
    		switch(k)
    		{
    			case 1:printf("%01lld
    ",ans);break;
    			case 2:printf("%02lld
    ",ans);break;
    			case 3:printf("%03lld
    ",ans);break;
    		}
    	}
    	return 0;
    }
    

    rp++

  • 相关阅读:
    Linux文件误删除恢复操作【转】
    segment fault异常及常见定位手段【转】
    Linux AUFS 文件系统【转】
    Linux MTD系统剖析【转】
    Linux UBI子系统设计初探【转】
    python笔记54-re正则匹配替换字符串(sub和subn)
    python笔记53-Leetcode面试题:请实现一个函数,把字符串 s 中的每个空格替换成"%20"
    咏南中间件支持客户端控制数据库事务
    mormot2 tbsonwriter
    firedac获取自增长字段值
  • 原文地址:https://www.cnblogs.com/wzc521/p/11626918.html
Copyright © 2011-2022 走看看