题解 CF451E 【Devu and Flowers】

洛谷传送门

cf传送门

贡献一个只用到基础组合数学+容斥的做法


假设每种花都是无穷多的。那么,方案数显然相当于 \(s\) 个小球,分 \(k\) 组,每组至少 \(0\) 个的方案数。

考虑初始加入 \(n\) 个小球,按每组至少 \(1\) 个进行插空分配,之后再抽走。方案数显然为 \(\dbinom{s+n-1}{n-1}\)

当然,这个分法显然有问题,因为每种花并不是无穷多的,这种分法可能导致一些花比限制条件更多。

现考虑某集合 \(S\) ,集合中的每种花都严格多于限制条件。则对于集合中的某种花 \(i\) ,选择第 \(i\) 种花的数量 \(>f_i\) ,即 \(\geq f_i+1\)

因此,不妨设 \(\displaystyle f_S=\sum_{i\in 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-1\leq 19\) ,直接暴力展开组合数即可,复杂度为 \(O(n\cdot 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;
}
posted @ 2021-02-08 15:40  JustinRochester  阅读(85)  评论(0编辑  收藏  举报