大組合數取模詳解


適用范圍:  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;
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM