[bzoj2839] 集合计数
题面
思路
首先,还是按照这类题目的套路分析
设函数$g(x)$表示交集至少大小为$x$的方案数
那么先组合数算选取$x$个数的方法,再对剩下的$n-x$个数算集合的集合(也就是集族咯)个数,可以得到$g(x)$的表达式
$g(x)=\binom{n}{x}(2{2{n-x}}-1)$
那么我们只要找到一个容斥函数$f(i)$,使得$\sum_{i=K}^n f(i)g(i)=ans$就好了
我们考虑恰好出现了$i$次的情况,在上述公式中被计算的次数$T(i)$
显然$T(x)=\sum_{i=0}^x \binom{x}{i} f(i)$
考虑二项式反演
$f(x)=\sum_{i=0}^x (-1)^{x-i} \binom{x}{i} T(i)$
然后因为我们最后要得到的是$ans$,那么显然$T(i)=[i==K]$
所以$f(x)=(-1)^{x-K} \binom{x}{K}
完美解决
代入原式得:
$ANS=\sum_{i=k}^n (-1)^{i-K} \binom{i}{K} \binom{n}{i} (2{2{n-i}}-1)$
预处理后可以$O(n)$得出解
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cassert>
#include<cmath>
#define ll long long
#define MOD 1000000007
using namespace std;
inline int read(){
int re=0,flag=1;char ch=getchar();
while(ch>'9'||ch<'0'){
if(ch=='-') flag=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
return re*flag;
}
ll f[1000010],finv[1000010],ppow2[1000010],n,k;
ll qpow(ll a,ll b){
ll re=1;
while(b){
if(b&1) re=re*a%MOD;
a=a*a%MOD;b>>=1;
}
return re;
}
void init(){
ll i;f[0]=f[1]=finv[0]=finv[1]=1;
for(i=2;i<=n;i++) f[i]=f[i-1]*i%MOD;
finv[n]=qpow(f[n],MOD-2);
for(i=n;i>2;i--) finv[i-1]=finv[i]*i%MOD;
ppow2[0]=2;//预处理2^(2^i)
for(i=1;i<=n;i++) ppow2[i]=ppow2[i-1]*ppow2[i-1]%MOD;
}
ll C(ll x,ll y){
return f[x]*finv[y]%MOD*finv[x-y]%MOD;
}
int main(){
n=read();k=read();ll ans=0,ch=1,i;
init();
for(i=k;i<=n;i++){
ans=(ans+ch*C(i,k)*C(n,i)%MOD*(ppow2[n-i]-1)%MOD+MOD)%MOD;
ch=-ch;
}
printf("%lld\n",ans);
}