贡献一个只用到基础组合数学+容斥的做法
假设每种花都是无穷多的。那么,方案数显然相当于 (s) 个小球,分 (k) 组,每组至少 (0) 个的方案数。
考虑初始加入 (n) 个小球,按每组至少 (1) 个进行插空分配,之后再抽走。方案数显然为 (dbinom{s+n-1}{n-1}) 。
当然,这个分法显然有问题,因为每种花并不是无穷多的,这种分法可能导致一些花比限制条件更多。
现考虑某集合 (S) ,集合中的每种花都严格多于限制条件。则对于集合中的某种花 (i) ,选择第 (i) 种花的数量 (>f_i) ,即 (geq f_i+1) 。
因此,不妨设 (displaystyle f_S=sum_{iin S}(f_i+1)) 。
考虑集合 (S) 中每种花都严格多于限制条件的方案数,等价于先将 (s) 个小球中选出 (f_S) 个,剩余的部分分 (k) 组,每组至少 (0) 个。分配后,再将 (f_S) 个按需分配回去。因此,方案数为 (dbinom{s-f_S+n-1}{n-1}) 。
最后求每种花都不多于限制条件的方案数,则进行容斥:
有 (0) 个超限的 (-) 有 (1) 个超限的 (+) 有 (2) 个超限的 $cdots $
(displaystyle ans=sum_{S}(-1)^{|S|}dbinom{s-f_S+n-1}{n-1})
由于 (n-1leq 19) ,直接暴力展开组合数即可,复杂度为 (O(ncdot 2^n))
【代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD=1e9+7;
inline ll fpow(ll a,ll x) { ll ans=1; for(a%=MOD;x;x>>=1,a=a*a%MOD) if(x&1) ans=ans*a%MOD; return ans; }
inline ll inv(ll a) { return fpow(a,MOD-2); }
//快速幂 & 逆元
ll n,s,f[1<<20],invn;//invn 为 (n-1)! 的逆元
inline ll C(ll n,ll m){
if(m<0||m>n) return 0;
ll ans=1;
for(int i=1;i<=m;i++)
ans=(n-i+1)%MOD*ans%MOD;
return ans*invn%MOD;
}
inline ll calc(ll S){
if(S&(S-1)) f[S]=f[S^(S&(-S))]+f[S&(-S)];
return C(s-f[S]+n-1,n-1);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin>>n>>s;
invn=1;
for(int i=1;i<=n-1;i++) invn=invn*i%MOD;
invn=inv(invn);
for(int i=1,s=1;i<=n;i++,s<<=1) cin>>f[s],++f[s];
ll Ans=0;
for(int i=0;!(i>>n);i++)
Ans=( Ans+calc(i)*((__builtin_popcount(i>>10)+__builtin_popcount(i&1023)&1)?-1:1) )%MOD;
//__builtin_popcount(i) 可以直接算出 i 二进制中 1 的个数,但要 i 相对较小的时候
Ans=(Ans%MOD+MOD)%MOD;
cout<<Ans;
cout.flush();
return 0;
}