标签:方法 中间 turn return scanf std 推导 问题: 垃圾
不超过 \(n\) 个球放入 \(m\) 个盒子方案数,盒子可以为空,求方案数。
网上有很多使用公式推导的方法,这里介绍一种纯隔板法分析的思路。
首先分析一个子问题:将 \(x\) 个球放进 \(y\) 个盒子的方案数,盒子可以为空。
由于 \(y-1\) 个板隔开 \(y\) 段,那么 \(y\) 个盒子等价于 \(y-1\) 个隔板。
画出 \(x+y-1\) 个位置,在这 \(x+y-1\) 个位置里面填入 \(y-1\) 个板子,剩下的位置放球。
如果板子是相邻的就说明这两块板隔开的盒子为空,反之这个盒子中球的个数就是这相邻两个板子中间的球的数量。
也就是说,“\(x\) 个球放进 \(y\) 个盒子的方案数,盒子可以为空”的方案数为 \(C(x+y-1,y-1)\) 或者 \(C(x+y-1,x)\)。
回到这道题,由于题目要求 “不超过”,那么我们定义第 \(m+1\) 个盒子为垃圾箱,加入我们在 \(m\) 个盒子里面放入 \(m‘(m‘\le m)\) 个球,那么垃圾箱里面就会放 \(m-m‘\) 个球。
通俗地讲,多余的球会放入垃圾箱。
那么,这道题就被我们转化为 “在 \(m+1\) 个盒子里面放入 \(n\) 个球”。
由于之前的推导,我们易知此题答案为 \(C(n+m,m)\)。
#include<cstdio>
#define int long long
const int MAXP=100000;
int T,n,m,p;
int fac[MAXP+5],inv[MAXP+5];
inline int INV(int a,const int mod){
int ret=1,n=mod-2;
for(;n>0;n>>=1){
if(n&1)ret=ret*a%mod;
a=a*a%mod;
}
return ret;
}
inline int C(const int n,const int m,const int p){
if(n<m)return 0;
return fac[n]*INV(fac[m],p)%p*INV(fac[n-m],p)%p;
}
inline int lucas(const int n,const int m,const int p){
if(m==0)return 1;
return lucas(n/p,m/p,p)*C(n%p,m%p,p)%p;
}
inline void init(const int lim){
fac[0]=fac[1]=inv[1]=1;//注意 fac[0]=1, 因为可能要用到 0 的阶乘
for(int i=2;i<=lim;++i)fac[i]=fac[i-1]*i%p;
// for(int i=2;i<=lim;++i)inv[i]=inv[p%i]*(p-p/i)%p;
// for(int i=2;i<=lim;++i)inv[i]=inv[i]*inv[i-1]%p;
}
signed main(){
scanf("%lld",&T);
while(T--){
scanf("%lld%lld%lld",&n,&m,&p);
init(p);
printf("%lld\n",lucas(n+m,n,p));
}
return 0;
}
标签:方法 中间 turn return scanf std 推导 问题: 垃圾
原文地址:https://www.cnblogs.com/Arextre/p/12756062.html