[bzoj4535]Kapita加强版 解题报告

本来是想看着这篇题解做的,看到如果考虑多项式,那么它只需要保存到k-1次项就开始自己yy了。(后面的看不懂了。。所以只好自己yy傻逼做法)

还是跟原题一样,先求 n!=2a(cmod2k) n!=5b(cmod5k) ,然后扩展欧几里得合并。(我一开始像那个题解里说的一样直接求不是2或5的倍数的数的阶乘。。然而我发现在递归的时候会有问题。可能是我做法不太一样吧。= =)

2和5的求法是一样的,这里我直接用p表示对于任意一个质数的情况。
b=logpni=1npi
设f(n)表示[1,n]中与除掉p的倍数的所有数的乘积。则 c=logpni=0f(npi) .注意到这个只有 logpn 项,是比较少的。
但是关键在于如何求f(n)呢?这就要用到多项式了!
考虑 g0(x)=p1i=1(x+i) ,它的0次项系数就是[1,p)中所有数的乘积,而 g0(x)g0(x+p) 的0此项系数就是[1,2p)中与除了p的倍数的乘积了,所以我们发现这个东西是可以倍增的!而最神奇的地方在于注意到每次代入x它都是p的倍数,所以 xk0modpk,kk ,所以在多项式中我们需要保留前k项就可以了。于是f(n)就求出来了。
不过这里有个有点奇怪的问题,注意到我们最后其实是要求g(0),而它等于 x0 的系数,但这样就出现了 00 。我感觉其实 00 也是可以出现的,不能出现只是约定而已,我们也可以约定它是存在的,所以这里并不会有问题。

第一步枚举需要 O(log2n) ,倍增需要 O(log2n) ,多项式有k项,所以它的代入和相乘的代价都是 O(k2 乘法的代价 ) ,注意到这里的乘法是会爆long long的,如果使用 O(log2n) 的快速乘的话,时间复杂度是 O(k2log32n)108 ,注意到这题时限2s,而且显然是有10个点的。所以并不能过,改进的方法是把快速乘换成 O(1) 的(之前我并不会)。
这样时间复杂度就是 O(k2log22n) ,但是注意到这个时间复杂度在k=9(原题)的情况下并不会快多少。。(非常的稳定)而糖教的做法似乎在k=9的时候非常的快。。不知道他怎么搞的。(orz tangjz)原来因为我们是在求模 pk 意义下的,所以我们可以像原题做法一样,预处理 f(pk) ,而 f(n)=f(pk)npkf(nmodpk) ,这样就可以加速到 O(k3log2n) 了!

这里讲解(学习)一下 O(1) 快速乘。(骆可强在2009国家集训队论文《论程序底层优化的一些方法与技巧》中说这个会有精度误差,然而实际上并不会有)
我们考虑求 abmodm,a,b,m<1018
化式子: abmodm=ababmm
考虑用long double求 abm ,因为一般情况下long double是96位的(noi+cena都如此),所以求出来的 abm 与实际的不会超过1,设其求出来的为t。
|abtm|<2m<264 ,所以 abtmmod264abtm 。考虑直接爆long long地求 abtm ,实际上它就等价于在 mod264 地求ab-tm,所以我们爆long long地求出来的就是正确的ab-tm。
但是它并不一定等于 abmodm ,真正等于的是 (((abtm)modm)+m)modm

我在研究快速乘的时候因为跟double/long double打交道,遇到了很多神奇的问题。。
我感觉我的c++在用double运算的时候好像是自动转成long double运算的,因为如果我算 (double)ab/m ,那么就会得到正确答案,和 (long double)ab/m 得到的结果是一样的,而如果我用 tmp=(double)ab,tmp/m tmp=(double)a/m,tmpb tmp=(double)b/m,atmp 就都得不到正确答案。
long double不能读入\输出,而且还不能比较大小,比较大小的时候好像是会自动转成double。

顺便吐槽。。这题为什么叫Kapita,原题不是叫Kapital么。比原题少个l什么鬼。
代码:

#include<cstdio>
#include<iostream>
using namespace std;
#include<algorithm>
#include<cstring>
#include<cmath>
#include<iomanip>
typedef long long LL;
const LL N=1e15+5,M=1e15+5;
const int K=18;
const int Log_2=50,Log_10=18;
LL absol(LL x){
    return x>0?x:-x;
}
LL mul(LL sum,LL b,LL Mod){
    sum=(sum%Mod+Mod)%Mod,b=(b%Mod+Mod)%Mod;
    if((double)sum*b<=1e18)return sum*b%Mod;
    return ((sum*b-Mod*(LL)((long double)sum*b/Mod))%Mod+Mod)%Mod;
}
LL pow(LL prod,LL x,LL Mod){
    prod%=Mod;
    LL ans=1;
    for(;x;x>>=1){
        if(x&1)ans=mul(ans,prod,Mod);
        prod=mul(prod,prod,Mod);
    }
    return ans;
}
void ex_eu(LL a,LL b,LL &x,LL &y){
    if(b){
        ex_eu(b,a%b,y,x);
        y-=a/b*x;
    }
    else x=1,y=0;
}
LL com[Log_10+5][Log_10+5];
void build_com(){
    for(int i=Log_10;i>=0;--i)com[i][0]=1;
    for(int i=1;i<=Log_10;++i)
        for(int j=i;j;--j)
            com[i][j]=com[i-1][j-1]+com[i-1][j];
}
LL tmp[Log_10+5];
void mul(LL a[Log_10+5],LL b[Log_10+5],LL ans[Log_10+5],LL Mod){
    memset(tmp,0,sizeof(tmp));
    for(int i=Log_10;i>=0;--i)
        if(a[i])
            for(int j=i;j<=Log_10;++j)
                if(b[j-i])
                    tmp[j]=(tmp[j]+mul(a[i],b[j-i],Mod))%Mod;
    memcpy(ans,tmp,sizeof(tmp));
}
void getin(LL a[Log_10+5],LL m,LL ans[Log_10+5],LL Mod){
    LL power[Log_10+5];
    power[0]=1;
    for(int i=1;i<=Log_10;++i)power[i]=mul(power[i-1],m,Mod);

    memset(tmp,0,sizeof(tmp));
    for(int i=Log_10;i>=0;--i)
        for(int j=i;j>=0;--j)
            tmp[j]=(tmp[j]+mul(a[i],mul(com[i][j],power[i-j],Mod),Mod))%Mod;
    memcpy(ans,tmp,sizeof(tmp));
}
LL pol_2[Log_2+5][Log_10+5];
LL pol_5[Log_2+5][Log_10+5];
void build_pol(LL pol[Log_2+5][Log_10+5],int p){
    pol[0][0]=1;
    for(int i=1;i<p;++i){
        for(int j=i;j;--j)pol[0][j]=pol[0][j]*i+pol[0][j-1];
        pol[0][0]*=i;
    }

    LL Mod=pow(p,Log_10);
    LL now=p;
    for(int i=1;i<=Log_2;++i,now<<=1){
        getin(pol[i-1],now,pol[i],Mod);
        mul(pol[i],pol[i-1],pol[i],Mod);
    }
}
LL cal_rest(LL n,LL pol[Log_2+5][Log_10+5],int p,LL Mod){
    //printf("cal_rest(%I64d,%d)\n",n,p);

    LL rest=1;
    for(;n%p;--n)rest=mul(rest,n,Mod);

    LL ans[Log_10+5]={1},ined[Log_10+5];
    n/=p;
    LL ansnow=0,now=p;
    for(int i=0;n;++i){
        if(n&1){
            getin(pol[i],ansnow,ined,Mod);
            mul(ans,ined,ans,Mod);
            ansnow+=now;
        }
        n>>=1,now<<=1;
    }

    //printf("ans=%I64d*%I64d=%I64d\n",ans[0],rest,mul(ans[0],rest,Mod));

    return mul(ans[0],rest,Mod);
}
LL cal(LL n,LL &cnt,LL pol[Log_2+5][Log_10+5],int p,LL Mod){
    //printf("cal(%I64d,%d)\n",n,p);
    LL rest=cal_rest(n,pol,p,Mod);
    //cout<<rest<<endl;
    for(n/=p;n;n/=p){
        cnt+=n;
        rest=mul(rest,cal_rest(n,pol,p,Mod),Mod);

        //cout<<rest<<endl;
    }
    //puts("------");
    return rest;
}
struct FS{
    LL cnt_2,cnt_5;
    LL rest;
};
void out(FS node){
    printf("{cnt_2=%I64d,cnt_5=%I64d,rest=%I64d}\n",node.cnt_2,node.cnt_5,node.rest);
}
FS query(LL n,int k){

    //printf("-------query(%I64d,%d)\n",n,k);

    FS ans=(FS){0,0,1};

    LL Mod=pow(10,k),Mod_2=1LL<<k,Mod_5=Mod>>k;

    LL rest_2=cal(n,ans.cnt_2,pol_2,2,Mod_2);
    LL rest_5=cal(n,ans.cnt_5,pol_5,5,Mod_5);

    //printf("rest_2=%I64d\n",rest_2);
    //printf("rest_5=%I64d\n",rest_5);

    rest_2=mul(rest_2,pow(pow(5,(Mod_2>>1)-1,Mod_2),ans.cnt_5,Mod_2),Mod_2);
    rest_5=mul(rest_5,pow(pow(2,(Mod_5/5<<2)-1,Mod_5),ans.cnt_2,Mod_5),Mod_5);

    //printf("rest_2=%I64d\n",rest_2);
    //printf("rest_5=%I64d\n",rest_5);

    LL x,y;
    ex_eu(Mod_2,Mod_5,x,y);
    //cout<<x*Mod_2+y*Mod_5<<endl;
    ans.rest=mul(x,rest_5-rest_2,Mod_5)*Mod_2+rest_2;

    //printf("ans=");
    //out(ans);

    return ans;
}
int main(){
    freopen("bzoj_4535.in","r",stdin);

    build_com();
    build_pol(pol_2,2);
    build_pol(pol_5,5);

    LL n,m;
    int k;
    cin>>n>>m>>k;
    FS a=query(n+m,k),b0=query(n,k),b1=query(m,k);
    a.cnt_2-=b0.cnt_2+b1.cnt_2;
    a.cnt_5-=b0.cnt_5+b1.cnt_5;
    LL Mod=pow(10,k),phi=Mod/10<<2;
    a.rest=mul(a.rest,mul(pow(b0.rest,phi-1,Mod),pow(b1.rest,phi-1,Mod),Mod),Mod);
    cout<<setfill('0')<<setw(k)<<mul(pow(a.cnt_2>a.cnt_5?2:5,absol(a.cnt_2-a.cnt_5),Mod),a.rest,Mod)<<endl;
}

总结:
①long double的精度是不小于long long的精度的。
②以后都写O(1)的快速乘好了。
③爆long long/int的运算等价于 mod264/232 的运算。
00 也是可以的。
⑤总结一下组合数取模吧!
Cmnmodpk(mn, p是质数 )) .(模任何一个数都可以先求这个,然后中国剩余定理合并,代价是 O(log2nloglogn) )。
情况一:n,m都比较小( n,m104 ),用公式 Cmn={1Cm1n1+Cmn1,m=0,m0 递推求解即可。
情况二:n很大,m比较小( n1018,m107 ),可以用公式 Cmn=nm+1mCm1n 递推求解,有逆元直接求,如果没有逆元的话就把p提出来即可。

以下是n,m都很大的情况 (n,m1018)
情况三:p是比较小的质数且k=1 (p107) 。比较简单的方法是直接上lucas定理: Cmn=CmpnpCmmodpnmodp 。(证明很简单也很巧妙,主要是利用了组合数与二项式定理之间的关系)时间复杂度 O(p+logpn) 。当然也可以用下面的方法。
情况四: pk 比较小 (pk107) 。可以用ontak2013原题的方法,求 n!=pac(p/|c) ,时间复杂度 O(pk+logpnlog2n)
情况四:p比较小,k很大 p106,klogp1018 。就可以倍增多项式乘法来做,时间复杂度 O(pk+k2log2n+logpn(p+log2nk2)) .
情况五:p,k都很大。我听说picks可以用什么fft+多项式的东西来做。。我以后有朝一日会fft了就去学一学吧。

你可能感兴趣的:(数论,组合数学)