D-小柒分糖果_牛客练习赛135(组合数,阶乘,逆元)
小柒作为班主任,最近为自己班的学生购买了 n 种糖果,第 i 种糖果有 a [ i] 颗。班级里一共有 m 名学生,小柒可以决定每种糖果发几颗,发给哪几名学生,每名学生获得的糖果数量。小柒想知道他一共有多少种发糖果的方法。由于答案可能很大,请将答案对 (10^9)+7 取模后输出。 如果两种发糖果的方法中,有任何一名学生得到的某种糖果数量不同,则认为这两种方法不同。
第一行输入两个整数 n,m(1≤n,m≤105) 代表糖果的种类数、班级的人数。 第二行输入 n个正整数 a1,a2,…,an(1≤ai≤105) 代表每种糖果的数量。
3 1 1
1 1 1
输出:8
思路
m个人应该怎么分呢?
我的第一反应是隔板法,觉得有点复杂,放弃了。紧接着开始想“给第二个人分配是在给第一个人分配的基础上进行的,就像是抽奖一样”,于是列了式子:
3个糖果,分给第一个人有四种可能:0,1,2,3
分给第二个人:1/4*(3+2+1+0)=5/2
那么分给两个人的可能数为:4*5/2=10
接着开始找规律,无果,还是需要向下推。后面太复杂,用代码打表,计算将3个糖果分给m个人,分别有几种可能。
4
10
20
35
56
84
第一想法就是肯定是一个组合数,因为之前牛客上这道题:河南萌新联赛2024第(四)场:河南理工大学 C 岗位分配 I 马拉松-CSDN博客,当时就是因为对组合数不够敏感,没有及时想到隔板法导致没有A。这次便幸运了,这正是:
C
n
+
m
m
C_{n+m}^{m}
Cn+mm
看来第一反应是对的,又是 隔板法
思路和那道”岗位分配“如出一辙,建议移步
同样的,板子不能插到同一位置,总要分隔。所以”岗位分配“便将每个岗位少分配一个人。而此题每个人得到的最少糖果为0,这时候可以将m个人最少得到的糖果看作-1,那么糖果数就可以看作”n+m“
以n=3,m=3举例
@ @ @ @ @ @
此时,有6个小球。需要分给3个人,每个人至少一个,允许有空闲
那么有几个隔板,几个空位呢?
@__ @__ @__ @__ @__ @__
(假定:最后一部分为空闲小球)有六个空位,第一个小球前面不插,保证每人至少一个。
有3个隔板,将小球分为4部分。
OK,以上就是关于组合数的分析。
数据范围较大,需乘法逆元+快速幂
请看: 2024ccpc全国邀请赛(郑州) 补题(乘法逆元)-CSDN博客
优化:初始化阶乘数组,求组合数(乘法逆元法)
请看: 求组合数(递推法、乘法逆元、卢卡斯定理、分解质因数)-CSDN博客
代码
const int N=1e6;//1e5,段错误,不懂
int mod=1e9+7;
int a[N],jiex[N];
int qpow(int a, int b,int c)//快速幂
{
int ans=1;
while(b)
{
if(b%2==1) ans=ans*a%c;
a=a*a%c;
b/=2;
}
return ans%c;
}
void solve()
{
int n,m,sum=1,mx=0;
cin>>n>>m;
fir(i,1,n)
{
cin>>a[i];
mx=max(mx,a[i]);
}
jiex[0]=1;
fir(i,1,mx+m)//初始化阶乘数组,mx+m很必要
{
jiex[i]=i*jiex[i-1]%mod;
}
int nn,niyuan;
fir(i,1,n)
{
nn=a[i]+m;
if(nn<m) continue;
niyuan=qpow(jiex[nn-m]*jiex[m]%mod,mod-2,mod);
sum=(sum*jiex[nn]%mod*niyuan%mod)%mod;
}
cout<<sum<<'\n';
}
signed main()
{
IOS
int t=1;
//cin>>t;
while(t--)
solve();
return 0;
}
总结
这道题也算是给之前的一个交代,去思考”岗位分配“那道题,整理组合数的几种求解方法,这些并不是徒劳。不过还是忘记了”逆元之间的计算推导“,这次没能用上,至于当时的理解思考,也已忘记了。