组合数模板(卢卡斯 + 扩展卢卡斯)

求 C(n,m) %p的结果。当n太大时,我们无法打阶乘表,所以我们采用Lucas定理来求组合数。

一    n , m ,mod 较大,mod为素数

 例题:mod<=1e9,n<=1e9, m<=1e4 

#include <iostream>
#include <cstdio>
#define LL long long
using namespace std;

LL quick( LL a, LL b, LL p )
{
    LL ans = 1LL;
    while( b )
    {
        if( b & 1 ) ans = ( ans * a ) % p ;
        a = ( a * a ) % p ;
        b >>= 1 ;
    }
    return ans ;
}

LL fac( LL x, LL p )
{
    LL ans = 1;
    for( int i = 2 ; i <= x ; i ++ ) ans = ( ans * i ) % p;
    return ans;
}

LL C( LL n, LL m, LL p )
{
    if( m < 0 || n < m ) return 0;
    if( m == 0 ) return 1 ;
    m = min( m, n - m );
    LL a = 1LL, b = 1LL;
    for( LL i = 1 ; i <= m ; i++ )
    {
        a =   a * (n - i + 1) % p ;
        b = ( b * i ) % p;
    }
    return a * quick( b, p - 2, p ) % p ;
}


LL Lucas( LL n, LL m, LL p )
{
    return m ? C( n % p, m % p, p ) * Lucas( n / p, m / p, p ) % p : 1 ;
}

int main()
{
    int T;
    LL n, m, p;
    scanf("%d",&T);
    while( T-- )
    {
        scanf("%lld%lld%lld",&n,&m,&p);
        LL res = Lucas( n, m, p );
        printf("%lld\n",res);
    }
    return 0 ;
}

扩展卢卡斯(p不一定为素数)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll quick_mod(ll a,ll b,ll m) //快速幂
{
    ll ans=1ll;
    while(b)
    {
        if(b&1) ans=ans*a%m;
        b>>=1;
        a=a*a%m;
    }
    return ans;
}

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(a%b==0)
    {
        x=0ll;
        y=1ll;
        return b;
    }
    ll v,tx,ty;
    v=exgcd(b,a%b,tx,ty);
    x=ty;
    y=tx-a/b*ty;
    return v;
}

ll inv(ll a,ll p)
{
    if(!a) return 0ll;
    ll x,y;
    exgcd(a,p,x,y);
    x=(x%p+p)%p;
    return x;
}

ll Mul(ll n,ll pi,ll pk)
{
    if(!n) return 1ll;
    ll ans=1ll;
    for(ll i=2; i<=pk; i++)
        if(i%pi) ans=ans*i%pk;
    ans=quick_mod(ans,n/pk,pk);
    for(ll i=2; i<=n%pk; i++)
    {
        if(i%pi) ans=ans*i%pk;
    }
    return ans*Mul(n/pi,pi,pk)%pk;
}

ll exlucas(ll n,ll m,ll p,ll pi,ll pk)
{
    if(m>n) return 0ll;
    ll a=Mul(n,pi,pk);
    ll b=Mul(m,pi,pk);
    ll c=Mul(n-m,pi,pk);
    ll k=0ll,ans=0ll;
    for(ll i=n; i; i/=pi) k+=i/pi;
    for(ll i=m; i; i/=pi) k-=i/pi;
    for(ll i=n-m; i; i/=pi) k-=i/pi;
    ans=a*inv(b,pk)%pk*inv(c,pk)%pk*quick_mod(pi,k,pk)%pk;
    return ans*(p/pk)%p*inv(p/pk,pk)%p;     //中国剩余定理  a[i]*M*x  余数*其他个个素数的乘积*x
}

int main()
{
    ll n,m,p,ans=0;
    while(cin>>n>>m>>p)
    {
        for(ll x=p,i=2; i<=p; i++)
        {
            if(x%i==0)
            {
                ll pk=1ll;
                while(x%i==0) pk*=i,x/=i;
                ans=(ans+exlucas(n,m,p,i,pk))%p;
            }
        }
        cout<<ans<<endl;
        ans=0;
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值