【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 }

 

 

 

posted @ 2012-12-16 19:48  Mr. Ant  阅读(2475)  评论(0编辑  收藏  举报