一道不错的题,对排列组合能力的要求较高

题意:给定s个相同的小球放在n个不同的盒子里,可以不放,每个盒子有一个放的上限,求一共有多少种放法

解析:首先考虑没有上限的情况,这里比较好解决,采用隔板法,可以计算出放法为CF451E-LMLPHP

看到网上很少有对这个隔板法进行详解的,这里稍微做一下解释:

隔板法,顾名思义,就是采用放置隔板的方法来进行分组方式的计算,在这里,由于每个小球都是相同的,所以唯一产生不同的可能性就是不同盒子里放的小球个数不同,那么这就转化为了一个分组问题:将s个小球用n-1个隔板分为n组,问方案数有多少

但有一个问题,就是盒子可以不放,如果单纯用隔板的话对于不放的盒子是处理不了的

所以我们再引入n个小球,要求每个盒子至少放一个,这样就能解决这个问题了

所以答案是CF451E-LMLPHP

接下来,由于现在有上限,所以直接用这个答案是显然不对的。

于是我们考虑容斥。

那么显然,容斥方法就是至少有0个超过上限-至少有1个超过上限+至少有2个超过上限...

那么我们以至少有一个超过上限的算法举例:

显然,我们首先要枚举谁超过了上限,那么这一步可以使用状压来实现,将超过上限的点的状态记作1,其余点状态记作0即可,这样的思想也可以解决更多超过上限的情况。

接下来,我们考虑:至少一个超过上限的表现就是这一个盒子至少放了上限+1个球,而其余盒子随意,不做要求,故我们可以理解为首先将上限+1个球确定地放在这一个盒子里,然后把剩下的球随便放

那么剩下的球随便放的方法同样满足公式CF451E-LMLPHP

(当然,球的总数会发生改变)

这样问题就解决了。

(其实推导之后有一个总的排列组合公式可以解决这个问题,但基本上看到这个公式正常人就跑了,所以这里只提出思想,因为事实上在实现的时候还是基于这个思想而不是基于最后的结论公式的)

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define mode 1000000007
#define ll long long
using namespace std;
ll pow_mul(ll x,ll y)
{
ll ans=1;
while(y)
{
if(y&1)
{
ans*=x;
ans%=mode;
}
x*=x;
x%=mode;
y>>=1;
}
return ans;
}
ll n;
ll f[25];
ll s;
int main()
{
scanf("%lld%lld",&n,&s);
for(int i=1;i<=n;i++)
{
scanf("%lld",&f[i]);
}
ll ret=0;
for(int i=0;i<(1<<n);i++)
{
int flag=1;
ll tot=s;
for(int j=0;j<n;j++)
{
if(i&(1<<j))
{
flag=-flag;
tot-=(f[j+1]+1);
}
}
if(tot<0)
{
continue;
}
ll s1=1,s2=1;
for(int j=1;j<=n-1;j++)
{
s1*=(tot+j)%mode;
s1%=mode;
s2*=j;
s2%=mode;
}
s1*=pow_mul(s2,mode-2);
ret+=flag*s1;
ret=((ret%mode)+mode)%mode;
}
printf("%lld\n",ret);
return 0;
}
04-26 02:20