【清华集训2017模拟】Catalan

本文介绍了一种高效计算大数卡特兰数并进行特定取模的方法。利用该方法,即使在面对非常大的数值时也能快速准确地得到结果。文中详细解释了如何将阶乘分解为5的幂次和其他因子,通过递归计算和预处理技巧减少计算复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

Cnmod3814697265625(518)其中Cn为卡特兰数第n项
n<=10^18,T<=10

Solution

这么大的组合数取模啊。。。。以前真没见过
首先我们知道Ans=Cn2nn+1
根据套路我们只需要把n!写成5^e*f的形式,然后就可以用逆元直接算了
但是如何计算?
首先我们把所有5的倍数提取出来,那么有n5个5,并且剩下的数变成了n5!
这样就可以递归下去计算了
那么如何处理剩余的部分?
我们在处理乘法的时候忽略5的倍数,设L=mo=59,那么我们相当于要求

i=0nL1j=0L1(iL+j)

假如我们把i,l当做常数,那么我们可以发现后面的东西可以写成(a*i*L+b)的形式
a和b只和L有关,可以预处理
那么这样我们处理出a,b,再处理出一个前缀积,这个东西就可以O(1)计算了
预处理的复杂度是O(L)的
多出来的那一部分直接处理就好了

但是这样只能处理n<=mo的情况,n>mo的情况处理不了,怎么办呢?
我们发现n>mo的时候我们可以把1~n的数按mo分类,其中每一组在模意义下都是同余的
这样直接快速幂计算即可
总复杂度O(L+T log^2N)

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
typedef double db;
const ll mo=3814697265625;
const int L=1953125;
struct note{
    int e;ll f;
    note(int _e=0,ll _f=0) {e=_e;f=_f;}
};
ll f[L][2],sum[L],n;
ll mult(ll a,ll b) {
    ll a1=a/L,b1=a%L,a2=b/L,b2=b%L;
    ll tmp=a1*a2%mo*L%mo*L%mo;
    (tmp+=a1*L%mo*b2%mo)%=mo;
    (tmp+=a2*L%mo*b1%mo)%=mo;
    (tmp+=b1*b2%mo)%=mo;
    return tmp;
}
ll mi(ll x,ll y) {
    ll z=1;
    for(;y;y/=2,x=mult(x,x))
        if (y&1) z=mult(z,x);
    return z;
}
ll F(ll n) {
    ll a=n/L,b=n%L,ans=1;
    if (a) ans=mult(ans,sum[a-1]);
    ans=mult(ans,(mult(mult(f[b][1],a),L)+f[b][0])%mo);
    return ans;
}
note solve(ll n) {
    if (!n) return note(0,1);
    note x=solve(n/(ll)5),ans;
    if (n>mo) {
        ll a=n/mo,b=n%mo;
        ans.f=mult(x.f,mult(mi(F(mo),a),F(b)));
    } else ans.f=mult(x.f,F(n));
    ans.e=(ll)(x.e+n/(ll)5)%18;
    return ans;
}
void exgcd(ll a,ll b,ll &x,ll &y) {
    if (!b) {
        x=1;y=0;
        return;
    }
    ll xx,yy;
    exgcd(b,a%b,xx,yy);
    x=yy;y=xx-a/b*yy;
}
ll get_inv(ll x) {
    ll a,b;
    exgcd(x,mo,a,b);
    return ((a%=mo)+=mo)%=mo;
}
int main() {
    freopen("catalan.in","r",stdin);
    freopen("catalan.out","w",stdout);
    f[0][0]=1;
    fo(i,1,L-1) {
        if (!(i%5)) {
            f[i][0]=f[i-1][0];
            f[i][1]=f[i-1][1];
            continue;
        }
        f[i][0]=mult(f[i-1][0],(ll)i);
        f[i][1]=(f[i-1][0]+mult(f[i-1][1],(ll)i))%mo;
    }
    sum[0]=f[L-1][0];
    fo(i,1,L-1) sum[i]=mult(sum[i-1],(mult(mult(f[L-1][1],(ll)i),(ll)L)+f[L-1][0])%mo);
    int ty;
    for(scanf("%d",&ty);ty;ty--) {
        scanf("%lld",&n);

        note a=solve(2*n);
        note b=solve(n);
        (a.e+=18-2*b.e%18)%=18;
        ll ni=get_inv(b.f);
        a.f=mult(a.f,mult(ni,ni));

        ll nn=n+1;
        for(;!(nn%5);nn/=5) (a.e+=17)%=18;
        a.f=mult(a.f,get_inv(nn));

        ll c=mult(a.f,mi(5,a.e));
        printf("%lld\n",c);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值