[HDU3037] Saving Beans Lucas定理

传送门

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
 
 
21 2 52 1 5
 
Sample Output
 
 
33

中文题目背景:

虽然冬天很远,松鼠不得不日夜工作以拯救豆子。他们需要充足的食物来度过那些漫长的寒冷日子。过了一段时间,松鼠家庭认为他们必须解决一个问题。他们认为他们会在不同的树上拯救豆子。然而,由于现在的食物不够,他们只吃不到豆子。他们想知道有多少种方法在N树中保存不超过M bean(它们是相同的)。

现在他们求助于你,你应该给他们答案。结果可能非常巨大,你应该输出模P,因为松鼠无法识别大数。

题目大意:

求在n棵树上摘不超过m颗豆子的方案,结果对p取模。

题解:

根据题意可以明白最终所求答案为,如果直接枚举i的话很显然会TLE。于是我们可以用一个很神奇的结论(组合恒等式)把答案化为,又因为模数p为质数,直接上普通lucas定理解决。

AC代码:

#include<iostream>
#include<cstdio>
#define ll long long 
using namespace std;
ll a[200005],p,t,n,m;
ll exgcd(ll a,ll b,ll &x,ll &y){//扩展欧几里得
	if(b){
		ll d=exgcd(b,a%b,y,x);
		y-=a/b*x;
		return d;
	}
	x=1;y=0;return a;
}
ll inv(ll x,ll mod){//求x在模mod下的逆元 
	ll anx,any;exgcd(x,mod,anx,any);return (anx+mod)%mod;
}
ll C(ll n,ll m){//求C(n,m)%p n<=10^5 m<=10^5 
	if(m>n)return 0;
	return a[n]*inv(a[m],p)%p*inv(a[n-m],p)%p;
}
ll lucas(ll n,ll m){//lucas定理求 C(n,m)%p n,m可以很大 
	if(!m)return 1;
	return C(n%p,m%p)*lucas(n/p,m/p)%p;
}
void prep(){//预处理n!%p 
	a[0]=1;
	for(ll i=1;i<=p;i++){
		a[i]=i*a[i-1]%p;
	}
}
void work(){
	scanf("%lld%lld%lld",&n,&m,&p);
	prep();
	printf("%lld\n",lucas(n+m,n));
}
int main(){
	scanf("%lld",&t);
	while(t--){
		work();
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值