hdu3037--Saving Beans --【lucas定理+逆元+插板法】

                                              Saving Beans

                        Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
                                                  Total Submission(s): 7817    Accepted Submission(s): 3151

 

Problem Description

Although winter is far away, squirrels have to work day and night to save beans. They need plenty of food to get through those long cold days. After some time the squirrel family thinks that they have to solve a problem. They suppose that they will save beans in n different trees. However, since the food is not sufficient nowadays, they will get no more than m beans. They want to know that how many ways there are to save no more than m beans (they are the same) in n trees.

Now they turn to you for help, you should give them the answer. The result may be extremely huge; you should output the result modulo p, because squirrels can’t recognize large numbers.

 

Input

The first line contains one integer T, means the number of cases.
Then followed T lines, each line contains three integers n, m, p, means that squirrels will save no more than m same beans in n different trees, 1 <= n, m <= 1000000000, 1 < p < 100000 and p is guaranteed to be a prime.

 

Output

You should output the answer modulo p.

 

Sample Input

 

2 1 2 5 2 1 5

 

Sample Output

 

3 3

Hint

Hint For sample 1, squirrels will put no more than 2 beans in one tree. Since trees are different, we can label them as 1, 2 … and so on. The 3 ways are: put no beans, put 1 bean in tree 1 and put 2 beans in tree 1. For sample 2, the 3 ways are: put no beans, put 1 bean in tree 1 and put 1 bean in tree 2.

 

题意:把不超过m个松果放到n个不同的盒子里,问有多少方法;

题解:

1)首先用隔板法求出把 i 个松果放到n个盒子里的方法数:

      (1):若要求每个盒子至少有一个,那么把 i 个松果排成一排,那么形成了 i-1 个可供插板的位置,同一个位置最多插入一个(保证每个盒子至少有一个),要想分到n个集合中,只需插入n-1个板,例如 i=5,n=3时,状态为1 2 1 2 1 2 1,其中1表示松果,2表示可以插板的位置;所以方法数为 C_{i-1}^{n-1} 

        (2):若盒子里可以为空,那么可以考虑先在n个盒子里每个盒子放上1个松果,所以问题就转化为将n+i个松果放到n个盒子的放法,并且每个盒子至少放一个,那么问题就转化为情况(1)了,其方法数为C_{n+i-1}^{n-1}     =     C_{n+i-1}^{i}

2)计算  \sum_{i=0}^{m}  C_{n+i-1}^{i}

      \sum_{i=0}^{m}    C_{n+i-1}^{i}    =C_{n-1}^{0}C_{n}^{1}C_{n+1}^{2} + .........+C_{n+m-1}^{m}

                                   =C_{n}^{0}C_{n}^{1}C_{n+1}^{2} +..........+C_{n+m-1}^{m}

                                   =C_{n+1}^{1}C_{n+1}^{2} +...........+ C_{n+m-1}^{m}

                                   .......

                                   =C_{n+m}^{m}

          至此,我们知道本题的答案就是  C_{n+m}^{m}  % p喽!

3)计算C_{n+m}^{m} % p

            那么问题又来了,n,m,数据范围那么大,怎么求组合数呢?那么这里要用到lucas定理逆元

           简单来说逆元是用来处理   \frac{a}{b}  % p 问题的,并且要求a,b<p,则有 \frac{a}{b}  % p =a*(b的逆元) % p;逆元可以用扩展欧几里得得到,也可以由费马小定理得到(要求p为质数),这题题目中说了p一定为质数,所以就选费马吧 = =;

         费马小定理(Fermat's little theorem)数论中的一个重要定理,在1636年提出,其内容为: 假如p是质数,且gcd(a,p)=1,那么a^{p-1} ≡1(mod p),即:假如a是整数,p是质数,且a,p互质(即两者只有一个公约数1),那么a的(p-1)次方除以p的余数恒等于1。意思很明了,由上方的公式很容易推导出:a*a^{p-2}≡1(mod p)对于整数a,p,a关于p的逆元就是a^{p-2},直接快速幂解之即可,但注意这个定理要求a,p互质!

      那么对于这题的数据范围,n,m是有可能大于p的,所以要用到lucas定理;

      lucas定理简单来说是指 C(m,n) % mod= C(m % mod,n % mod) * C(m / mod , n / mod) % mod;可以将一个比较大的组合数转化为较小的求解;一直转化至 m<p&&n<p 时就可以使用逆元求解了;

 

附代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

#define pii pair<int, int>
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define per(i,a,b) for(int i=a;i<=b;i++)
#define rep(i,a,b) for(int i=a;i>=b;i--)
#define all(x) x.begin(),x.end()
#define PER(i,x) for(auto i=x.begin();i!=x.end();i++)
#define PI acos(-1.0)
#define inf 0x3f3f3f3f
typedef long long ll;
const double eps=1.0e-5;
const int maxn=200000+10;

ll fac[100005];
ll t,n,m,p;

ll quick_pow(ll a,ll b,ll c)
{
	ll ans=1,tmp=a;
	while(b){
		if(b&1) ans=ans*tmp%c;
		tmp=tmp*tmp%c;
		b>>=1;
	}
	return ans;
}

ll calc(ll a,ll b,ll c)
{
	if(a<b) return 0;
	return fac[a]*quick_pow(fac[b],c-2,c)%c*quick_pow(fac[a-b],c-2,c)%c;
}

ll lucas(ll a,ll b,ll c)
{
	if(a<c&&b<c) return calc(a,b,c);
	else return calc(a%c,b%c,c)*lucas(a/c,b/c,c)%c;
}

void get_fac()
{
	fac[0]=1;
	per(i,1,p) fac[i]=fac[i-1]*i%p;
}

int main()
{
	scanf("%lld",&t);
	while(t--){
		scanf("%lld%lld%lld",&n,&m,&p);
		get_fac();
		printf("%lld\n",lucas(n+m,m,p));
	}
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值