[翻译向]阶乘模大质数

本文大部分翻译自http://min-25.hatenablog.com/entry/2017/04/10/215046,有改动。min_25牛逼

考虑经典问题:求$n!\bmod p$,p为一个大质数。

令$v=\lfloor \sqrt{n} \rfloor$,设$g_p(x)=\prod_{i=1}^p (x+i)$,那么我们想要求$g_v(0),g_v(v)...g_v((v-1)v)$。

考虑倍增地求,假设我们有了$g_d(0),g_d(v)...g_d(dv)$,那么$g_d$本身也唯一确定了,那么如何求出$g_{2d}(0)g_{2d}(v)...g_{2d}(2dv)$呢?

注意到$g_{2d}(x)=g_d(x)g_d(x+d)$,那么我们如果求出了$g_d((d+1)v),g_d((d+2)v)...g_d(2dv)$,以及$g_d(d),g_d(d+v)...g_d(2dv+d)$,那么我们就可以直接算出$g_{2d}(0),g_{2d}(v)...g_{2d}(2dv)$。

考虑假设给定了一个多项式h的$h(0),h(1)...h(d)$,如何求$h(k),h(1+k)...h(d+k)$。

进行拉格朗日插值,那么:

$\begin{aligned} h(m+k) &= \sum_{i=0}^{d} h(i) \prod _{j=0, i\ne j}^{d} \frac{m+k-j}{i-j} \\ &= \left(\prod_{j=0}^{d} (m+k-j)\right) \left( \sum_{i=0}^{d} \frac{h(i)}{i! (d-i)!(-1)^{d-i}} \cdot \frac{1}{m+k-i} \right) \end{aligned}$

前一个括号可以通过预处理$[k-d,k+d]$的阶乘前缀积得到。后面一看就是个卷积。$O(d\log(d))$。

注意:如果$k,1+k...d+k$与$0,1,...,d$有公共部分,那么分母会为0(虽然也可以处理,但是麻烦一点)。在本题中恰好不会。

接下来的事情就很简单了,令$h(x)=g_d(vx)$即可。

如何求$g_{d+1}(0),g_{d+1}(v)...g_{d+1}((d+1)v)$呢?前面到dv为止暴力添上一项,最后一项暴力。

这个做法复杂度是$O(\sqrt{n}\log(n))$,可以用威尔逊定理减小常数。比起传统的$O(\sqrt{n}\log^{1.5}(n)) \sim O(\sqrt{n}\log^2(n))$多点求值做法,这个做法常数小,还好写,不知道高到哪里去了。

测速:51nod 1387。如果n为偶数,答案为n!,否则为$\frac{n!}{2}$。这题有一个坑点在于fft长度只能到$2^{16}$,超出的需要拆成两段(虽然好像不会变慢就是了)。

#define SZ 666666
int MOD; ll w[2][SZ],G,fac[SZ],rfac[SZ];
inline ll qp(ll a,ll b)
{
    ll ans=1; a%=MOD;
    while(b)
    {
        if(b&1) ans=ans*a%MOD;
        a=a*a%MOD; b>>=1;
    }
    return ans;
}
inline ll org_root()
{
    static ll yss[2333];
    int yyn=0; ll xp=MOD-1;
    for(ll i=2;i*i<=xp;i++)
    {
        if(xp%i) continue;
        yss[++yyn]=i;
        while(xp%i==0) xp/=i;
    }
    if(xp!=1) yss[++yyn]=xp;
    ll ans=1;
    while(1)
    {
        bool ok=1;
        for(int i=1;i<=yyn;i++)
            if(qp(ans,(MOD-1)/yss[i])==1) {ok=0; break;}
        if(ok) return ans; ++ans;
    }
}
int K; ll rv;
inline void fftinit(int n)
{
    for(K=1;K<n;K<<=1);
    w[0][0]=w[0][K]=1;
    ll g=qp(G,(MOD-1)/K);
    for(int i=1;i<K;i++) w[0][i]=w[0][i-1]*g%MOD;
    for(int i=0;i<=K;i++) w[1][i]=w[0][K-i];
    rv=qp(K,MOD-2);
}
inline void fft(int* x,int v)
{
    for(int i=0;i<K;i++) (x[i]<0)?(x[i]+=MOD):0;
    for(int i=0,j=0;i<K;i++)
    {
        if(i>j) swap(x[i],x[j]);
        for(int l=K>>1;(j^=l)<l;l>>=1);
    }
    for(int i=2;i<=K;i<<=1)
        for(int l=0;l<i>>1;l++)
        {
            register int W=w[v][K/i*l],*p=x+l+(i>>1),*q=x+l,t;
            for(register int j=0;j<K;j+=i)
            {
                p[j]=(q[j]-(t=(ll)p[j]*W%MOD)<0)?(q[j]-t+MOD):(q[j]-t);
                q[j]=(q[j]>=MOD-t)?(q[j]-MOD+t):(q[j]+t);
            }
        }
    if(!v) return;
    for(int i=0;i<K;i++) x[i]=x[i]*rv%MOD;
}
ll ff[SZ]; int A[SZ],B[SZ],C[SZ];
inline void calc(int*h,int*o,int d,int k)
{
    int off=k-d-1; ff[0]=1;
    for(int j=1;j<=d+d+3;++j)
    {
        int s=(j+off)%MOD; if(s<0) s+=MOD;
        ff[j]=ff[j-1]*(ll)s%MOD;
    }
    fftinit(d+d+d+5);
    memset(A,0,sizeof(A[0])*K);
    memset(B,0,sizeof(B[0])*K);
    for(int i=0;i<=d;++i)
    {
        A[i]=h[i]*(ll)rfac[i]%MOD*rfac[d-i]%MOD;
        if((d-i)&1) A[i]=(MOD-A[i])%MOD;
    }
    for(int i=0;i<=d+d;++i)
        B[i]=qp(i-d+k,MOD-2);
    if(K<=(1<<16))
    {
        fft(A,0); fft(B,0);
        for(int i=0;i<K;++i) C[i]=(ll)A[i]*B[i]%MOD;
        fft(C,1);
    }
    else
    {
        fftinit(K>>1);
        fft(A,0); fft(A+K,0);
        fft(B,0); fft(B+K,0);
        for(int i=0;i<K;++i)
            C[i+K]=(A[i]*(ll)B[i+K]+(ll)B[i]*A[i+K])%MOD;
        for(int i=0;i<K;++i)
            C[i]=A[i]*(ll)B[i]%MOD;
        fft(C,1); fft(C+K,1);
    }
    for(int i=0;i<=d;++i)
    {
        //i+k-d...i+k
        o[i]=C[i+d]*ff[i+k-off]%MOD
        *qp(ff[i+k-d-off-1],MOD-2)%MOD;
        (o[i]<0)?(o[i]+=MOD):0;
    }
}
int V; ll rV;
int aa[SZ],bb[SZ];
inline void work(int x,vector<int>&v)
{
    if(x==0) {v.pb(1); return;}
    if(x&1)
    {
        work(x-1,v);
        for(int i=0;i<v.size();++i)
            v[i]=(ll)v[i]*(i*V+x)%MOD;
        ll p=1;
        for(int i=1;i<=x;++i)
            p=p*(V*x+i)%MOD;
        v.pb(p); return;
    }
    int d=x>>1; work(d,v);
    for(int i=0;i<=d;++i) aa[i]=v[i];
    calc(aa,aa+d+1,d,d+1);
    calc(aa,bb,d+d,d*rV%MOD);
    v.resize(x+1);
    for(int i=0;i<=x;++i)
        v[i]=aa[i]*(ll)bb[i]%MOD;
}
inline ll gfac_(int x)
{
    V=sqrt(x); rV=qp(V,MOD-2);
    vector<int> tmp; work(V,tmp); ll ans=1;
    for(int i=0;i<V;++i) ans=ans*tmp[i]%MOD;
    for(int i=V*V+1;i<=x;++i) ans=ans*i%MOD;
    return ans;
}
inline ll gfac(int x)
{
    if(x>=MOD) return 0;
    if(x>MOD-x-1)
    {
        int s=qp(gfac(MOD-x-1),MOD-2);
        if(x%2);else s=-s; return s;
    }
    return gfac_(x);
}
int main()
{
    int n; scanf("%d%d",&n,&MOD);
    fac[0]=1; for(int i=1;i<SZ;++i) fac[i]=fac[i-1]*i%MOD;
    rfac[SZ-1]=qp(fac[SZ-1],MOD-2);
    for(int i=SZ-1;i>=1;--i) rfac[i-1]=rfac[i]*i%MOD;
    G=org_root(); int ans=gfac(n)%MOD;
    if(n&1) ans=ans*(ll)((MOD+1)/2)%MOD;
    ans%=MOD; if(ans<0) ans+=MOD;
    printf("%d\n",int(ans));
}

原文地址:https://www.cnblogs.com/zzqsblog/p/8408691.html

时间: 2024-10-25 04:23:16

[翻译向]阶乘模大质数的相关文章

素数筛 codevs 1675 大质数 2

1675 大质数 2 时间限制: 1 s 空间限制: 1000 KB 题目等级 : 钻石 Diamond 题解 查看运行结果 题目描述 Description 小明因为没做作业而被数学老师罚站,之后数学老师要他回家把第n个质数找出来. 小明于是交给聪明的你.请你帮忙![wikioi-1530] …………………………以上为背景………………………… 老师怀疑小明仅仅是找到第n个质数,于是又叫小明把1到n以内(不包括n)的质数全部找出来.小明又找到了你…… 输入描述 Input Description

1675 大质数 2

1675 大质数 2 时间限制: 1 s 空间限制: 1000 KB 题目等级 : 钻石 Diamond 题目描述 Description 小明因为没做作业而被数学老师罚站,之后数学老师要他回家把第n个质数找出来. 小明于是交给聪明的你.请你帮忙![wikioi-1530] …………………………以上为背景………………………… 老师怀疑小明仅仅是找到第n个质数,于是又叫小明把1到n以内(不包括n)的质数全部找出来.小明又找到了你…… 输入描述 Input Description 一个正整数n. (

codevs——1530 大质数

1530 大质数 时间限制: 1 s 空间限制: 1000 KB 题目等级 : 黄金 Gold 题解 题目描述 Description 小明因为没做作业而被数学老师罚站,之后数学老师要他回家把第n个质数找出来.(1<=n<=100000) 老师随机写了几个数,交给了小明.小明百度找了很久,都没能解决.现在交给聪明的你.请你帮忙! ———————————————————————————————————————————— 简单描述:把第n个质数找出来. 输入描述 Input Description

codevs——1675 大质数 2

1675 大质数 2 时间限制: 1 s 空间限制: 1000 KB 题目等级 : 钻石 Diamond 题解 题目描述 Description 小明因为没做作业而被数学老师罚站,之后数学老师要他回家把第n个质数找出来. 小明于是交给聪明的你.请你帮忙![wikioi-1530] …………………………以上为背景………………………… 老师怀疑小明仅仅是找到第n个质数,于是又叫小明把1到n以内(不包括n)的质数全部找出来.小明又找到了你…… 输入描述 Input Description 一个正整数n

hihocoder 1287 : 数论一&#183;Miller-Rabin质数测试 大质数判定

时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho最近突然对密码学产生了兴趣,其中有个叫RSA的公钥密码算法.RSA算法的计算过程中,需要找一些很大的质数. 小Ho:要如何来找出足够大的质数呢? 小Hi:我倒是有一个想法,我们可以先随机一个特别大的初始奇数,然后检查它是不是质数,如果不是就找比它大2的数,一直重复,直到找到一个质数为止. 小Ho:这样好像可行,那我就这么办吧. 过了一会儿,小Ho拿来了一张写满数字的纸条. 小Ho:我用程序随机生成了一些初

Miller_Rabin大质数检验

质数检验有不少算法,一般使用的质数检验复杂度是\(O(\sqrt{n})\): 又如线性筛可以在\(O(n)\)的时间内求出所有1~n的质数 但是,当n非常大,连\(O(\sqrt{n})\)的复杂度也难以接受时,上述算法便不能满足要求 这篇blog记录了一些关于Miller_Rabin算法的内容 大家都知道的著名的费马小定理: \[a^{p-1}\equiv1\pmod p\] 其中\(a,p\)互质 我们猜想,任意选取\(a\),如果一个数\(p\)满足以上式子,那么它就很有可能是一个质数

codevs 2530大质数

链接:http://codevs.cn/problem/1530/ 解题思路: 这个题最关键的剪枝还是 因子小于平方根,但不是像原来那样用. 逆转思维,与其说判断哪些是质数,不如说判断哪些不是质数,更简单,更效率. 所有的合数都有一个共同的特点,就是能被拆成质因子. 那么已经出现的质因子,迟早有一次会成为一个合数的因子. 那么直接拿要判断的数挨个被质数除一遍,就可以直接判断是不是合数了. 写到这,前三个点就过了,至于最后一个100000,就是开头提到的剪枝. 不管怎样,一个合数里面的质因子一定也

分解大质数模板(复杂度小于sqrt(n))

//POJ 1811 #include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <time.h> using namespace std; typedef __int64 lld; lld ran() { return rand() << 16 | rand(); } lld gcd(lld a, lld b) { retu

组合数取模(转载)

本文转自:http://blog.csdn.net/skywalkert/article/details/52553048 0. 写在前面 在程序设计中,可能会碰到多种类型的计数问题,其中不少涉及到组合数的计算,所以笔者写下这么一篇文章,期望能解决一些常规的组合数求模问题.以下部分内容改编自AekdyCoin的<组合数求模>,而且为了感谢他对(懵懂的)笔者的启发,这篇文章的标题与其文章相同.另外,感谢Picks将多项式运算的技巧在中国进行推广,感谢51nod提供了许多有趣的数论题目,感谢fot