大组合数取模详解

适用范围:  p是一个素数,且p不能超过10^5(大约)

基础知识:

        Lucas定理:

        

      即将m转化为p进制,每一位数是m0,m1..,n也转化为p进制,n0,n1...

      C(m,n)==C(m0,n1)*C(m1,n2)*...%p;

      例如:m=100,n=50,p=17;

      m0=m%17=15;m1=(m/17)%17=5;

      n0=n%17=16;n1=(n/17)%17=2;(就是普通的进制转化)

      C(100,50)=C(15,16)*C(5,2)%p=0;(注意,当ni>mi时,结果为零,可以直接结束运算);

        组合数公式

          

        

 

 

        扩展欧几里德---传送门

实现思路:利用Lucas定理将大组合数转化成几个小组合的乘积,由于p只有10^5数量,可以预处理1到p阶剩的结果。接下来就是求(A/B)%p

     因为(A%p)/(B%p)!=(A/B)%p(处理阶乘时,已对p取模,100000!,64位都没有用),但(A/B)%p=(A*B^-1)%p会成立,这是结论,理由我也不知道,

     B^-1不是(1/B),是B的模逆元,利用扩展欧几里德---传送门求得,挺好理解,不懂套模板好了。

例子:  http://acm.hdu.edu.cn/showproblem.php?pid=3037

代码实现:

    

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
__int64 a[10000],b[10000];
__int64 x,y;
__int64 fac[110000]={1,1};
__int64 p;
__int64 change(__int64 tt,__int64 p,__int64 *a)//转化进制
{
    __int64 i=0;
    while(tt)
    {
        a[i++]=tt%p;
        tt/=p;
    }
    return i;
}
void Extgcd(__int64 a,__int64 b)//扩展欧几理德求B的逆元
{
    if(b==0)
    {
        x=1;y=0;
    }
    else
    {
        Extgcd(b,a%b);
        __int64 temp=x;
        x=y;
        y=temp-(a/b)*y;
    }
}
__int64 CC(__int64 a,__int64 b)//求组合数
{
    if(a<b)
        return 0;
    if(a==b)
        return 1;
    __int64 i,j;
    __int64 tt;
    tt=b;
    b=fac[a];
    a=(fac[tt]*fac[a-tt]%p);
    Extgcd(a,p);
    x*=b;
    x%=p;
    if(x<0)
        x+=p;
    return x;
}
int main()
{
    __int64 i,j,k,t,n,m;
    __int64 counta,countb;
    __int64 sum=1;
    scanf("%I64d",&t);
    while(t--)
    {
        scanf("%I64d%I64d%I64d",&n,&m,&p);
        for(i=2;i<=p;i++)
            fac[i]=fac[i-1]*i%p;//预处理阶乘
        n=m+n;
        counta=change(n,p,a);//记录个数
        countb=change(m,p,b);
        sum=1;
        for(i=0;i<counta&&i<countb;i++)
        {
            sum=(sum*CC(a[i],b[i]))%p;
        }
        cout<<sum<<endl;
    }
}

转载于:https://www.cnblogs.com/bigcc/archive/2012/08/17/2644571.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值