卡特兰数-两个经典做法

这篇博客探讨了组合数学中的卡特兰数在序列问题中的应用,以及如何高效地进行组合数的模运算。通过三种不同的模板展示了在不同场景下求解组合数模p的方法,包括对大质数模运算、对任意素数模运算以及处理超大整数的Lucas定理。这些算法在时间复杂度上有所优化,适用于处理大规模数据。
摘要由CSDN通过智能技术生成

卡特兰数现在我能接触到的用处就是用于解决一个序列中,任意前缀和中1的数量不少于0的数量(在这个序列中,1与0的个数相等)
证明如下图:

卡特兰数 =(公式一) C(2* n , n) - C(2n , n- 1) = (公式二)C(2n , n) / (n + 1)
组合数的板子很多,我们这里简单说3个吧
模板一 :组合数求余大质数
求C(a,b)
时间复杂度: O(a * loga)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 2015;
int n,mod = 1e9 + 7;
int fact[N],infact[N];
int qmi(int a, int k)
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (ll)res * a % mod;
        a = (ll)a * a % mod;
        k >>= 1;
    }
    return res;
}
int main()
{
    int t,a,b,temp,res;

    infact[0] = fact[0] = 1;
    for(int i = 1; i < N; i ++)//打表阶乘求余和阶乘的逆元
    {
        fact[i] = (ll) fact[i - 1] * i % mod;
        infact[i] = (ll) infact[i - 1] * qmi(i , mod - 2) % mod;
    }
    cin >> t;
    while(t --)
    {
        scanf("%d %d",&a,&b);
        cout << (ll) fact[a] * infact[b] % mod * infact[a - b] % mod << endl;
    }
    return 0;
}

模板二:组合数对任意素数求余p (p 不定)
时间复杂度:O(n * logn)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 2e6 + 15;
typedef long long ll;
ll primes[N],cnt;
bool book[N];
ll n,mod;
void get_primes()
{
    for(int i = 2; i < N; i ++)
    {
        if(!book[i]) primes[cnt ++] = i;
        for(int j = 0; j < cnt && primes[j] * i < N; j ++)
        {
            book[primes[j] * i] = true;
            if(i % primes[j] == 0) break;
        }
    }
}
ll get(ll x,ll p)
{
    ll res = 0;
    
    while(x)
    {
        res += x / p;
        x /= p;
    }
    
    return res;
}
ll qmi(ll a, ll b)
{
    ll ans = 1;
    a = a % mod;
    while(b)
    {
        if(b % 2 == 1) ans = (ll)ans * a % mod;
        a = (ll)a * a % mod;
        b /= 2;
    }
    return ans;
}
ll C(ll a, ll b)
{
    ll p,t;
    ll res = 1;
    for(int i = 0; i < cnt; i ++)
    {
        p = primes[i];
        t = (ll)get(a,p) - get(b,p) - get(a-b,p);
        res = (ll) res * qmi(p,t) % mod;
    }
    return res;
}
int main()
{
    get_primes();
    scanf("%lld %lld",&n,&mod);
    ll a = 2*n,b = n,c = n-1;
    cout << ( C(a , b) - C(a , c) + mod) % mod << endl;
    return 0;
}

模板三:超大(1e18)组合数求余p(p为质数)
Lucas定理:C(a,b) = C(a % p, b % p) * C(a / p, b / p);
时间复杂度:接近logpN * logp

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int p;
int qmi(int a,int b)
{
    int cnt = 1;
    a %= p;
    while(b)
    {
        if(b & 1) cnt = (ll) cnt * a % p;
        a = (ll)a * a % p;
        b /= 2;
    }
    return cnt;
}
int C(int a, int b)
{
    int res = 1;
    int temp = min(a - b,b);
    for(int i = a, j = temp; j > 0; i -- ,j --)
    {
        res = (ll) res * i % p;
        res = (ll) res * qmi(j , p - 2) % p;
    }
    return res;
}
int Lucas(ll a,ll b)
{
    if(a < p && b < p) return C(a , b);
    else return (ll)C(a % p, b % p) * Lucas(a / p, b / p) % p;
}
int main()
{
    int t;
    cin >> t;
    ll a,b;
    while(t --)
    {
        scanf("%lld %lld %d",&a,&b,&p);
        cout << Lucas(a,b) % p<< endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值