卡特兰数/斯特林数/卢卡斯定理

卡特兰数

//卡特兰数

#include <bits/stdc++.h>
using namespace std;

int f[20];
int n;

int main()
{
    cin >> n;
    f[0] = f[1] = 1;
    for (int i = 2; i <= n; i++)
    {
        for (int j = 0; j < i; j++)
        {
            f[i] += f[j] * f[i - j - 1];
        }
    }
    cout << f[n];
    return 0;
}

斯特林数

//第一类斯特林数:
//给出 n  个不同的数,选出 m  个非空圆排列,问有多少种方案?
/*
原理:
定义s1[i][j]表示 i  个不同数形成 j 个圆排列的方案数。这个状态很明显可以由两个状态转移过来:
第一个:i − 1  个数形成 j − 1 个圆排列,那么新加入的数只需要新形成一个圆排列就可以了。
第二个:i − 1  个数形成 j 个圆排列,那么新的数必须加入到原来的 j 个排列中去。显然加入到原来的 i − 1 个数的任意一个数的左边都是不同的。
那么总的方案数就是s1[i][j]=s1[i-1][j-1]+s1[i-1][j]*(i-1)。
*/

typedef long long ll;

const int N = 105;
ll s1[N][N];
void stirling1()
{
    s1[1][1] = 1;
    for (int i = 2; i <= n; i++)
    {
        for (int j = 1; j <= i; j++)
        {
            s1[i][j] = s1[i - 1][j] * (i - 1) + s1[i - 1][j - 1];
        }
    }
}

//第二类斯特林数:
//给定 n  个不同的数,将这 n 个数分成 m  个非空的集合,问有多少种不同的方案?

/*
原理
和第一类斯特林数一样,s2[i][j]表示将 i  个数分成 j  个集合,也有两种情况:
第一个:i − 1  个数分成了 j − 1  个集合,那么新的数只需要放入新的几个就可以了。
第二个:i − 1  个数分成了 j 个集合,那么新的那个数就必须放入这 j  个集合任意一个。
*/

const int N = 105;
ll s2[N][N];
void stirling2()
{
    s2[1][1] = 1;
    for (int i = 2; i <= n; i++)
    {
        for (int j = 1; j <= i; j++)
        {
            s2[i][j] = s2[i - 1][j] * j + s2[i - 1][j - 1];
        }
    }
}

卢卡斯定理

//卢卡斯定理
//求C(n,n+m)mod(p)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

ll q_pow(ll a,ll b,ll p)//快速幂用来求逆元 
{
    ll ans=1;
    while(b) 
    {
        if(b&1)
            ans=(ans*a)%p;
        a=(a*a)%p;
        b>>=1;
    }
    return ans;
}

ll comb(ll a,ll b,ll p)//求组合数 
{
    if(a<b)    return 0;
    if(b==a) return 1;
    if(b>a-b) b=a-b;
    ll ans=1,ca=1,cb=1;
    for(int i=0; i<b; i++) 
    {
        ca=(ca*(a-i))%p;
        cb=(cb*(b-i))%p;
    }
    ans=(ca*q_pow(cb,p-2,p))%p;
    return ans;
}

ll lucas(ll a,ll b,ll p)//lucas 
{
    ll ans=1;
    while(a&&b&&ans) 
    {
        ans=(ans*comb(a%p,b%p,p))%p;
        a/=p;
        b/=p;
    }
    return ans%p;
}

int main() 
{
    ll t,n,m,p;
    cin>>t;
    while(t--) 
    {
        cin>>n>>m>>p;
        cout<<lucas(n+m,n,p)<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值