Lucac求组合数( 组合 FZU - 2020)

Lucas定理是用来求 C(n,m) mod p,p为素数的值。(注意:p一定是素数)
 
给出组合数C(n,m), 表示从n个元素中选出m个元素的方案数。例如C(5,2) = 10, C(4,2) = 6.可是当n,m比较大的时候,C(n,m)很大!于是xiaobo希望你输出 C(n,m) mod p的值!

Input

输入数据第一行是一个正整数T,表示数据组数 (T <= 100) 接下来是T组数据,每组数据有3个正整数 n, m, p (1 <= m <= n <= 10^9, m <= 10^4, m < p < 10^9, p是素数)

Output

对于每组数据,输出一个正整数,表示C(n,m) mod p的结果。

Sample Input

2
5 2 3
5 2 61

Sample Output

1
10
#include <cstdio>
#define ll long long

using namespace std;

ll mulit(ll a,ll b,ll m)
{
    ll ans=0;
    while(b)
    {
        if(b&1)
        {
            ans=(ans+a)%m;
        }
        a=(a<<1)%m;
        b>>=1;
    }
    return ans;
}

ll quick_mod(ll a,ll b,ll m)
{
    ll ans=1;
    while(b)
    {
        if(b&1)
        {
            ans=mulit(ans,a,m);
        }
        a=mulit(a,a,m);
        b>>=1;
    }
    return ans;
}

ll comp(ll a,ll b,ll m)
{
    if(a<b)
    {
        return 0;
    }
    if(a==b)
    {
        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)%m;
        cb=cb*(b-i)%m;
    }
    ans=ca*quick_mod(cb,m-2,m)%m;
    return ans;
}

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

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

模板 

A、B是非负整数,p是质数。AB写成p进制:A=a[n]a[n-1]...a[0],B=b[n]b[n-1]...b[0]。
则组合数C(A,B)与C(a[n],b[n])*C(a[n-1],b[n-1])*...*C(a[0],b[0])  modp同余

即:Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p) 

以求解n! % p为例,把n分段,每p个一段,每一段求的结果是一样的。但是需要单独处理每一段的末尾p, 2p, ...,把p提取出来,会发现剩下的数正好又是(n / p)!,相当于划归成了一个子问题,这样递归求解即可。

posted @ 2019-08-14 21:31  weilong13  阅读(185)  评论(0)    收藏  举报