bzoj2839: 集合计数
本来是想学二项式反演的怎么我直接容斥就搞出了后面的柿子勒??
首先当组合题做,考虑每一个大小为k的最终交集,那么就有C(n,k)种情况,对于每一种交集,可以选出2^(n-k)种包含这个集合的集合
如果令g(k)=C(n,k)*sigema(1~2^(n-k))i C(2^(n-k),i)的话,这并不是交集大小至少为k的方案数,比如k=1时,集合是{A,B,C},{A}{B}{C}都是一个合法交集,但是选择的时候可以选出3次{A,B,C},所以我们不能直接用最基本的容斥 f(k)=sigema(k~n)i (-1)^(i-k)*g(k)来计算恰好为k的方案数
考虑对于大小为i的集合,对于当前k,它能够产生贡献的次数C(i,k)
于是f(k)=sigema(k~n)i (-1)^(i-k)*g(k)*C(i,k)就可以啦
然后这个就是二项式反演的形式。。。莫名其妙搞出来了。。。
假如从二项式反演的角度思考这个问题,首先我们能求g,然后希望得到的是交集大小为k的方案数f(k)
考虑对于每个交集为i的方案对于g(k)的贡献
和上面的思路几乎一样,因为每个交集为i的方案会被C(i,k)个交集为k的集合转移到
所以g(k)=sigema(k~n)i C(i,k)*f(i)
对于具体实现,注意nlogn会被卡,求阶乘逆元的时候可以先把inv[n]算出来再反过来推前面的
考虑如何算g,我们知道C(0,n)+……+C(n,n)=2^n的,可以变成C(n,k)*(2^(2^(n-k))-1)
只需要预处理2^(2^(n-k))
稍微化一下,2^(2^(n-k))=2^(2^(n-k-1)*2)=( 2^(2^(n-k-1)) )^2,这样也可以递推了
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; typedef long long LL; const int maxn=1e6+100; const LL mod=1e9+7; LL quick_pow(LL A,LL p,LL mo) { LL ret=1; while(p!=0) { if(p%2==1)ret=ret*A%mo; A=A*A%mo;p/=2; } return ret; } //---------------------------def---------------------------------------------- LL fac[maxn],fac_inv[maxn]; LL C(int n,int m){return fac[n]*fac_inv[m]%mod*fac_inv[n-m]%mod;} LL g[maxn],b[maxn]; void solve(int n) { fac[0]=1;fac_inv[0]=1; for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod; fac_inv[n]=quick_pow(fac[n],mod-2,mod); for(int i=n-1;i>=1;i--)fac_inv[i]=fac_inv[i+1]*(i+1)%mod; //......init...... b[n]=2; for(int i=n-1;i>=0;i--)b[i]=b[i+1]*b[i+1]%mod; for(int i=0;i<=n;i++) g[i]=C(n,i)*(b[i]-1)%mod; } //---------------------------prepare---------------------------------------------- int main() { // freopen("a.in","r",stdin); // freopen("a.out","w",stdout); int n,k; scanf("%d%d",&n,&k); solve(n); LL fk=0; for(int i=k;i<=n;i++) fk=(fk+(((i-k)%2==0)?1:-1)*C(i,k)* g[i])%mod; printf("%lld\n",(fk+mod)%mod); return 0; }
pain and happy in the cruel world.