Lucac求组合数( 组合 FZU - 2020)
Lucas定理是用来求 C(n,m) mod p,p为素数的值。(注意:p一定是素数)
给出组合数C(n,m), 表示从n个元素中选出m个元素的方案数。例如C(5,2) = 10, C(4,2) = 6.可是当n,m比较大的时候,C(n,m)很大!于是xiaobo希望你输出 C(n,m) mod p的值!
Input
输入数据第一行是一个正整数T,表示数据组数 (T <= 100) 接下来是T组数据,每组数据有3个正整数 n, m, p (1 <= m <= n <= 10^9, m <= 10^4, m < p < 10^9, p是素数)
Output
对于每组数据,输出一个正整数,表示C(n,m) mod p的结果。
Sample Input
2 5 2 3 5 2 61
Sample Output
1 10
#include <cstdio> #define ll long long using namespace std; ll mulit(ll a,ll b,ll m) { ll ans=0; while(b) { if(b&1) { ans=(ans+a)%m; } a=(a<<1)%m; b>>=1; } return ans; } ll quick_mod(ll a,ll b,ll m) { ll ans=1; while(b) { if(b&1) { ans=mulit(ans,a,m); } a=mulit(a,a,m); b>>=1; } return ans; } ll comp(ll a,ll b,ll m) { if(a<b) { return 0; } if(a==b) { return 1; } if(b>a-b) { b=a-b; } ll ans=1,ca=1,cb=1; for(int i=0;i<b;i++) { ca=ca*(a-i)%m; cb=cb*(b-i)%m; } ans=ca*quick_mod(cb,m-2,m)%m; return ans; } ll lucas(ll a,ll b,ll m) { ll ans=1; while(a&&b) { ans=(ans*comp(a%m,b%m,m))%m; a/=m; b/=m; } return ans; } int main() { int T; ll n,m,p; scanf("%d",&T); while(T--) { scanf("%lld %lld %lld",&n,&m,&p); ll ans=lucas(n,m,p); printf("%lld\n",ans); } return 0; }
模板
A、B是非负整数,p是质数。AB写成p进制:A=a[n]a[n-1]...a[0],B=b[n]b[n-1]...b[0]。
则组合数C(A,B)与C(a[n],b[n])*C(a[n-1],b[n-1])*...*C(a[0],b[0]) modp同余
即:Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p)
以求解n! % p为例,把n分段,每p个一段,每一段求的结果是一样的。但是需要单独处理每一段的末尾p, 2p, ...,把p提取出来,会发现剩下的数正好又是(n / p)!,相当于划归成了一个子问题,这样递归求解即可。

浙公网安备 33010602011771号