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

 

 

 

你可能感兴趣的:(cas)