bzoj2839 集合计数 容斥

博客围绕有N个元素的集合展开,要从其2N个子集中取若干集合,使交集元素个数为K,求方案数并对1000000007取模。通过容斥原理,设g[k]和f[k],经推导得出fk的表达式,可O(n)求恰好k的答案。

Description


一个有N个元素的集合有2N个不同子集(包含空集),现在要在这2N个集合中取出若干集合(至少一个),使得
它们的交集的元素个数为K,求取法的方案数,答案模1000000007。(是质数喔~)

Solution


这个容斥就很简单了
考虑设g[k]表示至少k个相同的答案,f[k]表示恰好k个相同的答案
显然有gk=∑i=kn(ik)fkg_k=\sum\limits_{i=k}^{n}{\binom{i}{k}f_k}gk=i=kn(ki)fk
考虑g[]的含义,不难得到gk=(nk)⋅(22n−k−1)g_k=\binom{n}{k}\cdot\left(2^{2^{n-k}}-1\right)gk=(kn)(22nk1)
然后有一个神奇的变换就是fk=∑i=kn(−1)i−k(ik)gkf_k=\sum\limits_{i=k}^n{{(-1)}^{i-k}\binom{i}{k}g_k}fk=i=kn(1)ik(ki)gk
这是一个反演,证明的话带入一下就可以了。。
于是我们就可以O(n)O(n)O(n)求恰好k的答案了

没有交,本地拍了一下似乎没毛病

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)

typedef long long LL;
const int MOD=1000000007;
const int N=200005;

LL fac[N],inv[N],f[N],g[N];

LL C(int n,int m) {
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}

LL ksm(LL x,LL dep,int MOD) {
	LL res=1;
	for (;dep;dep>>=1) {
		(dep&1)?(res=res*x%MOD):0;
		x=x*x%MOD;
	}
	return res;
}

int main(void) {
	freopen("data.in","r",stdin);
	freopen("myp.out","w",stdout);
	fac[0]=fac[1]=1; rep(i,2,N-1) fac[i]=fac[i-1]*i%MOD;
	inv[0]=inv[1]=1; rep(i,2,N-1) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
	rep(i,2,N-1) inv[i]=inv[i-1]*inv[i]%MOD;
	int n,k; scanf("%d%d",&n,&k);
	rep(i,1,n) g[i]=C(n,i)*(ksm(2,ksm(2,n-i,MOD-1),MOD)-1)%MOD;
	rep(j,k,n) {
		LL tmp=C(j,k)*g[j]%MOD;
		if ((j-k)&1) f[k]=(f[k]+MOD-tmp)%MOD;
		else f[k]=(f[k]+tmp)%MOD;
	}
	printf("%lld", f[k]);
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值