【HDU 3037】大数组合取模之Lucas定理
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3037
题目大意:求在n棵树上摘不超过m颗豆子的方案,结果对p取模。
解题思路:
题目可以转换成 x1+x2+……+xn=m 有多少组解,m在题中可以取0~m。
利用插板法可以得出x1+x2+……+xn=m解的个数为C(n+m-1,m);
则题目解的个数可以转换成求 sum=C(n+m-1,0)+C(n+m-1,1)+C(n+m-1,2)……+C(n+m-1,m)
利用公式C(n,r)=C(n-1,r)+C(n-1,r-1) == > sum=C(n+m,m)。
现在就是要求C(n+m,m)%p。
因为n,m很大,这里可以直接套用Lucas定理的模板即可。
Lucas(n,m,p)=C(n%p,m%p,p)*Lucas(n/p,m/p,p); ///这里可以采用对n分段递归求解,
Lucas(x,0,p)=1;
将n,m分解变小之后问题又转换成了求(a/b)%p。
(a/b)%p可以转换成a*Inv(b,p) Inv(b,p)为b对p的逆元。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cmath> 5 #include <cstring> 6 using namespace std; 7 8 typedef long long lld; 9 lld n, m, p; 10 11 lld Ext_gcd(lld a,lld b,lld &x,lld &y){ 12 if(b==0) { x=1, y=0; return a; } 13 lld ret= Ext_gcd(b,a%b,y,x); 14 y-= a/b*x; 15 return ret; 16 } 17 lld Inv(lld a,int m){ ///求逆元 18 lld d,x,y,t= (lld)m; 19 d= Ext_gcd(a,t,x,y); 20 if(d==1) return (x%t+t)%t; 21 return -1; 22 } 23 24 lld Cm(lld n, lld m, lld p) ///组合数学 25 { 26 lld a=1, b=1; 27 if(m>n) return 0; 28 while(m) 29 { 30 a=(a*n)%p; 31 b=(b*m)%p; 32 m--; 33 n--; 34 } 35 return (lld)a*Inv(b,p)%p; ///(a/b)%p 等价于 a*(b,p)的逆元 36 } 37 38 int Lucas(lld n, lld m, lld p) ///把n分段递归求解相乘 39 { 40 if(m==0) return 1; 41 return (lld)Cm(n%p,m%p,p)*(lld)Lucas(n/p,m/p,p)%p; 42 } 43 44 int main() 45 { 46 int T; 47 cin >> T; 48 while(T--) 49 { 50 scanf("%lld%lld%lld",&n,&m,&p); 51 printf("%d\n",Lucas(n+m,m,p)); 52 } 53 return 0; 54 }