bzoj 2839 集合计数 容斥\广义容斥

LINK:集合计数

容斥简单题 却引出我对广义容斥的深思。

一直以来我都不理解广义容斥是为什么 在什么情况下使用。

给一张图:

avatar

这张图想要表达的意思就是这道题目的意思 而求的东西也和题目一致。

特点:求出某个集合恰好为k的个数。

转换:求出集合>=k的个数或者<=k的个数 从而使用广义容斥容斥出来答案。

关于>=k个数 如上图可见 又很多重复的地方 而广义容斥也是在这么多重复的地方使用的 而并非严格>=k的个数。

换个说法 >=k的方案数 可能有一些存在重复 但是其特点是>=k 关于这个特点可以利用二进制的子集关系表现出来。

如 S1,S2都是恰好为k的 他们都能生成S3这个==k+1的集合 此时可以发现 S3被S1生成一次 被S2生成一次。所以所谓的>=k的方案数其中有一部分是子集的互相生成重复。

广义容斥就是利用这一点来计算的。

转到题目 不难发现 符合上面定义的>=k方案数为 \(C(n,k)(2^{2^{n-k}}-1)\)

套广义容斥的式子即可求出答案 值得注意的是 \(2^{n-k}\)可以由欧拉定理%(mod-1).

这道题还是一个简单容斥的类型。

可以发现所有的>=k的方案数为 \(C(n,k)(2^{2^{n-k}}-1)\)

此时讨论 关于选出的k个子集固定时 此时生成的方案除掉这k个交集可能还存在其他交集 -1个交集+2个交集-...

这样套简单容斥的式子也行。值得注意的是这个讨论实在k个子集固定时的讨论。

广义容斥 code:

const ll MAXN=1000010,N=17;
ll n,k;
ll fac[MAXN],inv[MAXN];
inline ll ksm(ll b,ll p,ll pp)
{
	ll cnt=1;
	while(p)
	{
		if(p&1)cnt=cnt*b%pp;
		b=b*b%pp;p=p>>1;
	}
	return cnt;
}
inline ll C(ll a,ll b){return a<b?0:fac[a]*inv[b]%mod*inv[a-b]%mod;}
signed main()
{
	freopen("1.in","r",stdin);
	get(n);get(k);fac[0]=1;
	rep(1,n,i)fac[i]=fac[i-1]*i%mod;
	inv[n]=ksm(fac[n],mod-2,mod);
	fep(n-1,0,i)inv[i]=inv[i+1]*(i+1)%mod;
	ll ans=0;
	rep(k,n,i)
	{
		ans=(ans+(((i-k)&1)?-1:1)*(C(n,i)*(ksm(2,ksm(2,n-i,mod-1),mod)-1))%mod*C(i,k))%mod;
	}
	putl((ans+mod)%mod);
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/chdy/p/12797898.html