题解 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_{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;
}
原文地址:https://www.cnblogs.com/JustinRochester/p/14389107.html