首页 > 编程语言 >快速阶乘算法

快速阶乘算法

时间:2023-02-26 15:44:56浏览次数:36  
标签:int sq len 算法 阶乘 include 快速 mod

快速阶乘。这个都不会我怕不是废了。

首先看阶乘的形式可以变成一堆形如

\[g(x)=\prod_{i=1}^v(x+i) \]

的多项式的点值乘积。于是 \(v=\lfloor\sqrt n\rfloor\),那么我们就要

\[\prod_{i=0}^{v-1}g(vi) \]

的值。

考虑倍增处理问题。设 \(g_d=\prod_{i=1}^d(x+i)\),那么我们最终需要的就是 \(g_v\) 的 前 \(v\) 项点值。我们知道 \(g_d\) 是个 \(d\) 次多项式,可以用 \(d+1\) 个点值表示。那么我们维护这些点值。

考虑如何倍增。我们发现

\[g_{2d}(x)=g_d(x)g_d(x+d) \]

那么我们每次倍增使用一次连续点值平移求出 \(g_d(x)\) 的 \(2d+1\) 个点值,再使用一次连续点值平移求出 \(g_d(x+d)\) 的 \(2d+1\) 个点值,乘一下就行了。

不想写任意模数,于是去了 loj 交的。然而跑的贼慢。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
#define int long long
using namespace std;
const int mod=1000391835649,sq=1<<20;
int n,m,wl,w[4200010],jc[4200010],inv[4200010],mjc[4200010],minv[4200010],f[4200010];
inline int mul(int a,int b){
    return ((a&(sq-1))*b+(((a>>20)*b%mod)<<20))%mod;
}
#define get(n) wl=1;while(wl<n)wl<<=1
#define add(x,y) (x+y>=mod?x+y-mod:x+y)
#define sub(x,y) (x<y?x-y+mod:x-y)
int qpow(int a,int b){
	int ans=1;
	while(b){
		if(b&1)ans=mul(a,ans);
		a=mul(a,a);
		b>>=1;
	}
	return ans;
}
void init(int n){
    int t=1;
    while((1<<t)<n)t++;
    t=min(t-1,24ll);
    w[0]=1;w[1<<t]=qpow(qpow(7,3*4969),1<<24-t);
    for(int i=t;i;i--)w[1<<i-1]=mul(w[1<<i],w[1<<i]);
    for(int i=1;i<(1<<t);i++)w[i]=mul(w[i&(i-1)],w[i&-i]);
}
void DIF(int a[],int n){
    for(int mid=n>>1;mid>=1;mid>>=1){
        for(int i=0,k=0;i<n;i+=mid<<1,k++){
            for(int j=0;j<mid;j++){
                int x=mul(a[i+j+mid],w[k]);
                a[i+j+mid]=sub(a[i+j],x);
                a[i+j]=add(a[i+j],x);
            }
        }
    }
}
void DIT(int a[],int n){
    for(int mid=1;mid<n;mid<<=1){
        for(int i=0,k=0;i<n;i+=mid<<1,k++){
            for(int j=0;j<mid;j++){
                int x=a[i+j+mid];
                a[i+j+mid]=mul(sub(a[i+j],x),w[k]);
                a[i+j]=add(a[i+j],x);
            }
        }
    }
    int inv=qpow(n,mod-2);
    for(int i=0;i<n;i++)a[i]=mul(a[i],inv);
    reverse(a+1,a+n);
}
void move(int n,int m,int f[],int g[]){
    mjc[0]=minv[0]=1;
	for(int i=1;i<=2*n+1;i++)mjc[i]=mul(mjc[i-1],m-n-1+i);
	minv[n<<1|1]=qpow(mjc[n<<1|1],mod-2);
	for(int i=2*n;i>=1;i--)minv[i]=mul(minv[i+1],m-n+i);
	get(n+1<<1);
	for(int i=0;i<=n;i++){
		f[i]=mul(mul(f[i],inv[i]),inv[n-i]);
		if(n-i&1)f[i]=sub(0,f[i]);
	}
	for(int i=0;i<=(n<<1);i++)g[i]=mul(minv[i+1],mjc[i]);
	DIF(f,wl);DIF(g,wl);for(int i=0;i<wl;i++)f[i]=mul(f[i],g[i]);DIT(f,wl);
	for(int i=n;i<=(n<<1);i++){
		g[i-n]=mul(mul(mjc[i+1],minv[i-n]),f[i]);
	}
    for(int i=n+1;i<=wl;i++)g[i]=0;
    for(int i=0;i<wl;i++)f[i]=0;
}
void solve(int n){
    f[0]=1;f[1]=sq+1;
    static int g[4200010],tmp[4200010];
    for(int len=2;len<=n;len<<=1){
        for(int i=0;i<=(len>>1);i++)tmp[i]=f[i];
        move(len>>1,(len>>1)+1,tmp,g);
        for(int i=(len>>1)+1;i<=len;i++)f[i]=g[i-(len>>1)-1],g[i-(len>>1)-1]=0;
        for(int i=0;i<=len;i++)tmp[i]=f[i];
        move(len,mul(len>>1,qpow(n,mod-2)),tmp,g);
        for(int i=0;i<=len;i++)f[i]=mul(f[i],g[i]);
    }
}
void pre(int n){
    jc[0]=inv[0]=1;
    for(int i=1;i<=2*n+1;i++)jc[i]=mul(jc[i-1],i);
    inv[2*n+1]=qpow(jc[2*n+1],mod-2);
    for(int i=2*n;i>=1;i--)inv[i]=mul(inv[i+1],i+1);
    solve(n);
    for(int i=1;i<=n;i++)f[i]=mul(f[i],f[i-1]);
    for(int i=n+1;i>=1;i--)f[i]=f[i-1];f[0]=1;
}
int getjc(int n){
    int ret=n/sq,ans=f[min(ret,sq+1)];
    for(int i=min(ret,sq+1)*sq+1;i<=n;i++)ans=mul(ans,i);
    return ans;
}
signed main(){
    init(1<<22);pre(sq);
    int tim;scanf("%lld",&tim);
    while(tim--){
        scanf("%lld",&n);
        printf("%lld\n",getjc(n));
    }
    return 0;
}

标签:int,sq,len,算法,阶乘,include,快速,mod
From: https://www.cnblogs.com/gtm1514/p/17156803.html

相关文章