给 n,m,p ,求 Cn+mnmodp ,保证 p 为质数
Lucas 定理,用来求大组合数取模,定理为:
Cnmmodp=C⌊n/p⌋⌊m/p⌋Cnmodpmmodpmodp
边界条件:m=0 时,返回 1
时间复杂度:O(f(p)+g(n)logn) ,其中 f(n) 为预处理组合数的复杂度,g(n) 为单次求组合数的复杂度。
ll lucas(ll n, ll m, ll p){
if(!m)return 1;
return (C(n%p,m%p,p)*lucas(n/p,m/p,p))%p;
}
定理证明如下:
首先考虑 Cpnmodp 的值,Cpn=n!(p−n)!p! ,则
Cpnmodp=[n=0∨n=p]
从而在 modp 下:
(a+b)p=n=0∑pCpnanbp−n=n=0∑n[n=0∨n=p]anbp−n=ap+bpmodp
再考虑二项式 f(x)=(axn+bxm)pmodp :
(axn+bxm)p=apxpn+bpxpm=axpn+bxpmmodp
最后一行是因为费马小定理 ap−1≡1modp
再考虑二项式 (1+x)nmodp ,则 Cnm 为其 xm 项前的系数,有:
(1+x)n=(1+x)p⌊n/p⌋(1+x)nmodp=(1+xp)⌊n/p⌋(1+x)nmodp=m=0∑nC⌊n/p⌋⌊m/p⌋xp⌊m/p⌋Cnmodpmmodpxmmodp=m=0∑nC⌊n/p⌋⌊m/p⌋Cnmodpmmodpxp⌊m/p⌋+mmodp
因此 Cnmmodp=C⌊n/p⌋⌊m/p⌋Cnmodpmmodpmodp
其中求小范围组合数时用到的乘法逆元可以通过费马小定理求得
#include<iostream>
#include<cstdio>
#define MAXN 100010
using namespace std;
typedef long long ll;
int T,n,m,p;
ll a[MAXN];
ll pow(ll y,int z){
y%=p;ll res=1;
while(z){
if(z&1)res=res*y%p;
y=y*y%p;z>>=1;
}
return res;
}
ll C(ll n,ll m){
if(m>n)return 0;
return a[n]*pow(a[m],p-2)%p*pow(a[n-m],p-2)%p;
}
ll lucas(ll n,ll m){
if(!m)return 1;
return C(n%p,m%p)*lucas(n/p,m/p)%p;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d%d",&n,&m,&p);
a[0]=1;
for(int i=1;i<=p;i++)a[i]=(a[i-1]*i)%p;
printf("%lld\n",lucas(n+m,m));
}
return 0;
}