luogu P3200 [HNOI2009]有趣的数列|Catalan数|数论

本文详细解析了Luogu P3200题目的解决方案,通过数论分析得出Catalan数的计算公式,并介绍了如何利用欧拉筛、快速幂等算法高效求解大范围内的Catalan数,特别关注了取模运算和质数分解的处理。

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

luogu P3200 [HNOI2009]有趣的数列|Catalan数|数论

Problem

Problem

分析

分析题目可得:

答案取模前就是Catalan数:
\[ ans(取模前)=\frac{C_{n}^{2n}}{\left(n+1\right)}=\frac{2n!}{n!(n+1)!} \]
根据n的范围:[0,1e6]可知超long long

所以题目给出p来膜

众所周知,带有除法的式子是不能直接每一步进行求余的

这时候可能会想到逆元。

众所周知,逆元中膜的数是质数

所以逆元也不行。

众所周知,ans(取模前)一定是整数,观察ans表达式:

如果对2n!进行整数唯一分解,即:
\[ 2n!=a_1^{b_1}\cdot a_2^{b_2}\cdot a_3^{b_3}\cdot...\cdot a_i^{b_i} \]
同理对n!和(n+1)!进行整数唯一分解

如果2n!和n!与(n+1)!有相同分解ai,则2n!中次数一定大于n!和(n+1)!的次数(因为ans是整数)

所以ans可以进一步化解:
\[ ans=a_1^{b_1-b_1'-b_1''}\cdot a_2^{b_2-b_2'-b_2''}\cdot a_3^{b_3-b_3'-b_3''}\cdot a_i^{b_i-b_i'-b_i''} \]
于是问题变成对阶乘进行整数唯一分解

对阶乘n!进行整数唯一分解的方法:
\[ \exists 质数m_i\in[2,n!],cnt[m_i]=n/m_i+n/m_i^2+n/m_i^3+...+n/m_i^k,m^k\leqslant n \]
综上所述,程序分为三个部分:

1、欧拉筛求质数

2、快速幂

3、进行catalan计算

Code

1、欧拉筛求质数

void Euler(ll n){
    memset(isprime,1,sizeof(isprime));
    isprime[0]=isprime[1]=0;
    for(ll i=2;i<=n;i++){
        if(isprime[i]) m[++cnt]=i;
        for(ll j=1;i*m[j]<=n;j++){
            isprime[i*m[j]]=0;
            if(i%m[j]==0) break;
        }
    }
}

2、快速幂

ll qpow(ll a,ll b){
    ll ans=1,base=a;
    while(b){
        if(b&1) ans=ans*base%p;
        base=base*base%p;
        b>>=1;
    }
    return ans%p;
}

3、求Catalan数

ll catalan(ll n){
    ll ans=1;
    for(ll i=1;i<=cnt&&m[i]<=2*n;i++){
        ll cnt1=0,cnt2=0,cnt3=0;
        ll pm=m[i];
        while(pm<=2*n){
            cnt1+=2*n/pm,cnt2+=n/pm,cnt3+=(n+1)/pm;
            pm*=m[i];
        }
        ans=ans*qpow(m[i],cnt1-cnt2-cnt3)%p;
    }
    return ans;
}

代码:

/*
C2n-n/(n+1)
=2n!/n!(n+1)
*/
#include <cstdio>
#include <iostream>
#include <cstring>
#define ll long long 
using namespace std;
const int maxn=2e6+5;
ll n,p;
ll isprime[maxn],m[maxn],cnt=0;
ll qpow(ll a,ll b){
    ll ans=1,base=a;
    while(b){
        if(b&1) ans=ans*base%p;
        base=base*base%p;
        b>>=1;
    }
    return ans%p;
}
void Euler(ll n){
    memset(isprime,1,sizeof(isprime));
    isprime[0]=isprime[1]=0;
    for(ll i=2;i<=n;i++){
        if(isprime[i]) m[++cnt]=i;
        for(ll j=1;i*m[j]<=n;j++){
            isprime[i*m[j]]=0;
            if(i%m[j]==0) break;
        }
    }
}
ll catalan(ll n){
    ll ans=1;
    for(ll i=1;i<=cnt&&m[i]<=2*n;i++){
        ll cnt1=0,cnt2=0,cnt3=0;
        ll pm=m[i];
        while(pm<=2*n){
            cnt1+=2*n/pm,cnt2+=n/pm,cnt3+=(n+1)/pm;
            pm*=m[i];
        }
        ans=ans*qpow(m[i],cnt1-cnt2-cnt3)%p;
    }
    return ans;
}
int main(){
    scanf("%lld%lld",&n,&p);
    Euler(2*n);
    printf("%lld",catalan(n));
    return 0;
}

转载于:https://www.cnblogs.com/saitoasuka/p/10383949.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值