[bzoj2839] 集合计数

题面

BZOJ权限题

离线题库

思路

首先,还是按照这类题目的套路分析

设函数$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);
}

posted @ 2018-08-05 17:06  dedicatus545  阅读(199)  评论(0编辑  收藏  举报