组合数之Lucas定理
逆元
(A/B)%p问题
(A/B)%p==A*inv(B,p)%p;
证明:逆元t满足(B*t)%p=1。
那么 (A/B)%p=(A/B)*(B*t)%p=A*t%p;(这里A%B==0)
接下来求逆元:
gcd(a,b)=a*x+b*y=d; ==> a*x=d%b;
当d==1时: x为a%b的逆元, y为b%a的逆元。 一般模数都是素数。
组合数C(n,m)%p=n*(n-1)*(n-m+1)/m! %p 属于(a/b)%p模型。
那么可以利用这个模型来求解组合数。 c(n,m)=c(n,m-1)*(n-m+1)/m;
1 LL CM(LL n,LL m,LL p)
2 {
3 LL a=1,b=1;
4 if(m>n)
5 return 0;
6 //c(n,m)=c(n,m-1)*(n-m+1)/m; O(m) 求c(n,m)
7 while(m)
8 {
9 a=(a*n)%p;
10 b=(b*m)%p;
11 m--;
12 n--;
13 }
14 return (a*inv(b,p))%p;
15 }
Lucas定理
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)
// Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p) 递归求解
LL Lucas(LL n,LL m,LL p)
{
if(m==0)
return 1;
return (CM(n%p,m%p,p)*Lucas(n/p,m/p,p))%p;
}
1 #include <iostream>
2 #include <cstdio>
3 #include <cstring>
4 #include <cmath>
5 #include <algorithm>
6 using namespace std;
7 typedef long long LL;
8
9 void ex_gcd(LL a,LL b,LL &d,LL &x,LL &y)
10 {
11 if(b==0)
12 {
13 x=1;y=0;d=a;
14 }
15 else
16 {
17 ex_gcd(b,a%b,d,y,x);
18 y-=(a/b)*x;
19 }
20 }
21
22 LL inv(LL a,LL m)
23 {
24 LL d,x,y;
25 ex_gcd(a,m,d,x,y);
26 if(d==1)
27 return (x%m+m)%m; //x可能为负数
28 return -1;
29 }
30
31 LL CM(LL n,LL m,LL p)
32 {
33 LL a=1,b=1;
34 if(m>n)
35 return 0;
36 //c(n,m)=c(n,m-1)*(n-m+1)/m; O(m) 求c(n,m)
37 while(m)
38 {
39 a=(a*n)%p;
40 b=(b*m)%p;
41 m--;
42 n--;
43 }
44 return (a*inv(b,p))%p;
45 }
46
47 // Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p)
48 LL Lucas(LL n,LL m,LL p)
49 {
50 if(m==0)
51 return 1;
52 return (CM(n%p,m%p,p)*Lucas(n/p,m/p,p))%p;
53 }
54
55 int main()
56 {
57
58 int T;
59 scanf("%d",&T);
60 while(T--)
61 {
62 LL n,m,p;
63 scanf("%lld%lld%lld",&n,&m,&p);
64 printf("%lld\n",Lucas(n+m,m,p));
65 }
66 return 0;
67 }
Lucas定理中包含了两种求解组合数得算法。
一般:
对于大组合数取模,n,m不大于10^5的话,用逆元的方法,可以解决。
对于n,m大于10^5的话,那么要求p<=10^5,这样就是Lucas定理了,将n,m转化到10^5以内解。
浙公网安备 33010602011771号