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