容斥原理及证明

定理

设共有n个集合,Ai表示第i个集合,则所有集合的并集可表示成以下形式:

|A1A2An|=i=1n(1)i1|A1A2Ai|

证明

设某个元素被x个集合包含,显然地,其对左式的贡献为1,因为在并集中只计算一次。
考虑其对于右式的贡献,它会在这x个集合的所有子集中被计算到。其贡献为:

i=1xCxi(1)i1=i=1xCxi(1)i=1i=0xCxi(1)i=1i=0xCxi(1)i×1xi=1(11)x=1

由于所有元素对于左右两式的贡献均为1,综上即可证得等式成立。

推论

Aic表示Ai的补集,S表示全集,则:

|A1cA2cAnc|=|S||A1A2An|=|S|i=1n(1)i1|A1A2Ai|

常用于解决限制条件较繁复的问题。

相关练习

[JSOI2015]染色问题

题解

[click]

这三个限制可以逐个来解决,然后逐层相乘。
当至少有ij列不染色的规定为某ij列不染色的时候,设fk表示不用k种颜色的方案。
则:

(ck)(ck+1)(ni)(mj)=t=kc(tk)ft

左式的意义为,选k个颜色不用的方案数。由于还有不涂色的选择,所以并不能保证只有那几个没有用到。假设有一种方案恰好有t种颜色没用到,根据这样的选择方式,它就被计算了(tk)次。由此得出上式。
二项式反演一下,或者从单纯的容斥角度而言,即可得到:

fk=t=kc(1)tk(tk)(ct)(ct+1)(ni)(mj)

k=1cfk=k=1ct=kc(1)tk(tk)(ct)(ct+1)(ni)(mj)=t=1c(tk)(ct)(ct+1)(ni)(mj)k=1t(1)tk(tk)=t=1c(tk)(ct)(ct+1)(ni)(mj)((1)t+k=0t(1)tk1k(tk))=t=1c(1)t+1(ct)(ct+1)(ni)(mj)

合法的方案数即为:

(c+1)(ni)(mj)k=1cfk=k=0c(1)k(ck)(ck+1)(ni)(mj)

这样的方案数,还是会算重,同样因为有不涂色的选择会导致超过i行或j列是完全空白的。再进行容斥,即可得到:

i=0n(1)i(ni)j=0m(1)j(mj)k=0c(1)k(ck)(ck+1)(ni)(mj)=i=0nk=0c(1)i+k(ni)(ck)j=0m(mj)(1)j(ck+1)(ni)(mj)=i=0nk=0c(1)i+k(ni)(ck)j=0m[1+(ck+1)ni]m

如果不将后面的求和转化掉的话,复杂度是O(n3)。转化后对每一个ck+1的幂进行预处理,即可达到O(n2logn)的复杂度。

代码

[click]
#include <cstdio>
#include <cctype>
typedef long long ll;
const int p=1e9+7;
const int maxn=400+10;
int fac[maxn],inv[maxn],pow[maxn*maxn];

int max(int x,int y) {return x>y?x:y;}
int read()
{
	int res=0;
	char ch=getchar();
	while(!isdigit(ch))
		ch=getchar();
	while(isdigit(ch))
		res=res*10+ch-'0',ch=getchar();
	return res;
}
int power(int a,int n)
{
	int res=1;
	while(n)
	{
		if (n&1)
			res=(ll)res*a%p;
		a=(ll)a*a%p;
		n>>=1;
	}
	return res;
}
void prework(int n)
{
	fac[0]=1;
	for (int i=1;i<=n;i++)
		fac[i]=(ll)fac[i-1]*i%p;
	inv[n]=power(fac[n], p-2);
	for (int i=n-1;i>=0;i--)
		inv[i]=(ll)inv[i+1]*(i+1)%p;
}
int C(int n,int m)
{
	if (m>n)
		return 0;
	return (ll)fac[n]*inv[m]%p*inv[n-m]%p;
}
int main()
{
	int n=read(),m=read(),c=read();
	prework(max(max(n, m), c));
	int ans=0;
	pow[0]=1;
	for (int k=0;k<=c;k++)
	{
		for (int i=1;i<=n;i++)
			pow[i]=(ll)pow[i-1]*(c-k+1)%p;
		for (int i=0;i<=n;i++)
		{
			int mul=(ll)power(pow[n-i]-1, m)*C(n, i)%p*C(c, k)%p;
			if ((i^k)&1)
				ans-=mul;
			else
				ans+=mul;
			if (ans<0)
				ans+=p;
			else if (ans>=p)
				ans-=p;
		}
	}
	printf("%d\n",ans); 
	return 0;
}
posted @   hkr04  阅读(2575)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示