HDU 3037(隔板法+组合数+Lucas)

题意:求在n棵树上摘不超过m颗豆子的方案数,结果对p取膜。
思路:其实就相当于把i(0 <= i <= m)个球放入n个不同的盒子里,盒子可以为空。
很明显,需要用到隔板法。所以对于i个球,方案数为C(i+n-1, n-1)。
总方案数为C(n-1, n-1)+C(n, n-1)+...+C(n+m-1, n-1);
然后根据公式C(n, m) = C(n, n-m)得方案数为
C(n-1, 0)+C(n, 1)+...+C(n+m-1, m);
然后再根据公式C(n, m) = C(n-1, m)+C(n-1, m-1)得方案数为
  0+C(n-1, 0)+C(n, 1)+...+C(n+m-1, m)
= C(n, 0)+C(n, 1)+C(n+1, 2)+...+C(n+m-1, m)
= C(n+m, m)
此时方案数变为了C(n+m, m)%p;

由于n+m和m较大,且p比n+m和m小,且p为素数,所以这儿用Lucas定理求解即可。


Code1:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
int N, M, P;
LL quickM(LL a, LL b, LL p)
{
	LL ans = 1, base = a;
	while(b)
	{
		if(b&1) ans = ans*base%p;
		base = base*base%p;
		b >>= 1;
	}
	return ans;
}
LL C(LL n, LL m, LL p)
{
	if(n < m) return 0;
	if(n == m) return 1;
	if(m > n-m) m = n-m;
	LL fta = 1, ftb = 1;
	for(LL i = 0; i < m; ++i)
	{
		fta = fta*(n-i)%p;
		ftb = ftb*(m-i)%p;
	}
	return fta*quickM(ftb, p-2, p)%p;
}
LL Lucas(LL n, LL m, LL p)
{
	LL ans = 1;
	while(n && m && ans)
	{
		ans = ans*C(n%p, m%p, p)%p;
		n /= p; m /= p;
	}
	return ans;
}
int main()
{
	int t;
	scanf("%d", &t);
	for(int _ = 1; _ <= t; ++_)
	{
		scanf("%d %d %d", &N, &M, &P);
		printf("%lld\n", Lucas(N+M, M, P));
	}
	return 0;
}


然后还写了一份预处理阶乘逆元的,会TLE,每次都需要重新计算,而且预处理版的对于Lucas写起来也很难看,Lucas以后就像上面写吧,效率颇好。

Code2:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 100000;
int N, M, P;
LL fact[maxn+5], fiv[maxn+5], inv[maxn+5];
void init()
{
	fact[0] = fact[1] = 1;
	fiv[0] = fiv[1] = 1;
	inv[1] = 1;
	for(int i = 2; i <= maxn; ++i)
	{
		fact[i] = fact[i-1]*i%P;
		inv[i] = (-P/i+P)*inv[P%i]%P;
		fiv[i] = inv[i]*fiv[i-1]%P;
	}
}
LL Lucas(LL n, LL m, LL p)
{
	LL ans = 1;
	while(n && m && ans)
	{
		ans = ans*fact[n%p]%p*fiv[(n%p-m%p+p)%p]*fiv[m%p]%p;
		n /= p; m /= p;
	}
	return ans;
}
int main()
{
	int t;
	scanf("%d", &t);
	for(int _ = 1; _ <= t; ++_)
	{
		scanf("%d %d %d", &N, &M, &P); init();
		printf("%lld\n", Lucas(N+M, M, P));
	}
	return 0;
}


继续加油~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值