适用范围: 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; } }