容斥原理学习笔记

定义

一般指将统计问题分解为几个子问题,然后将几个问题的结果分别乘以一个容斥系数求和

注意容斥系数不一定是维恩图的$(-1)^k$之类的,可能包含组合数

经典容斥

给定或是可以求出:分别满足一个属性的数量,分别满足两个属性的数量,分别满足两个属性的数量......

要求恰好元素的总数之类的

这种比较简单,直接$O(2^n)$小学容斥公式解决即可。

但有些题的属性不明显,需要经过适当转化

例题:【BZOJ3589】动态树

组合容斥

共有n个元素

给定或是可以求出:f(k)=【钦定有k个元素满足,其他元素随意】的数量。

要求出g(k)=【恰好有k个元素满足】的数量

请注意g(k)并不是f()的后缀和。

设该问题的容斥系数为h(x),则

$g(k)=\sum_{i=0}^n h(i)*f(i)$

考虑列方程求解h(x)

我们希望恰好有题目给定的a个元素满足的情况被统计的次数恰好为1,即我们要让$T(x)=[x=a]$

考虑$g(k)=\sum_{i=0}^n  h(i)*f(i)$中,

【恰好有k个元素满足的情况】统计次数为$T(k)=\sum_{i=0}^k h(i)*(^k_i)$

意思是在钦定i个元素满足,但实际恰好有k个元素满足的情况中,钦定的元素可能是这k中的任意一个,所以要先选出k个中是哪i个被钦定了,并且不管钦定的是谁,方案数都不会变

题外话:请注意恰好一个的方案数不等于至少一个减去至少两个的方案数(至少k个相当于先在n中选k个,钦定这k个满足,然后其他乱选)

假设有一个方案满足1,2,那么它在至少一个的方案数中会被统计两次(分别是钦定1和钦定2满足),然而在2中只会被统计一次,如果要恰好满足更多个情况就更复杂了。所以要组合容斥解决。

现在接这个方程:$T(k)=\sum_{i=0}^k h(i)*(^k_i)$

二项式反演:$h(k)=\sum_{i=0}^k(-1)^{k-i}*(^k_i)*T(i)$

即:$h(k)=\sum_{i=0}^k(-1)^{k-i}*(^k_i)*[i=a]$

即若k>=a,则$h(k)=(-1)^{k-a}*(^k_a)$,否则等于0

那么$g(k)=\sum_{i=k}^n (-1)^{i-k}*(^i_k)*f(i)$

先处理出f(x),然后处理g(x)即可,$O(n)$

其实这本质就是设未知函数,解函数方程的思想

有些题目比较奇怪,可能不会像$T(x)=[x=a]$这么简单,可能是要求【恰好有a的倍数个元素满足】的数量之类的毒瘤玩意。这样就把$T(x)=[x=a]$改为$T(x)=[a|x]$,然后用单位根反演即可,比如【loj前夕】

例题代码

#include<bits/stdc++.h>
using namespace std;
#define mod 998244353
#define int long long
#define N 10000010
int fac[N],ifac[N],g[N];
int c(int n,int m)
{
	return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int qpow(int aa,int bb,int pp)
{
	int res=1;
	aa%=pp;
	while(bb)
	{
		if(bb&1) res*=aa,res%=pp;
		aa*=aa,aa%=pp;
		bb>>=1ll;
	}
	return res;
}
signed main()
{
	int n;
	cin>>n;
	int w1=qpow(3,(mod-1)/4,mod),inv4=qpow(4,mod-2,mod);
	fac[0]=ifac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
	ifac[n]=qpow(fac[n],mod-2,mod);
	for(int i=n-1;i;i--) ifac[i]=ifac[i+1]*(i+1)%mod;
	int w[4]={0,w1-1,(w1*w1-1)%mod,(w1*w1%mod*w1-1)%mod},wn[4]={1,1,1,1};
	int ans=0;
	g[n]=2;
	for(int i=n-1;i>=0;i--) g[i]=g[i+1]*g[i+1]%mod;
	for(int i=0;i<=n;i++)
	{
		int f=(wn[0]+wn[1]+wn[2]+wn[3])%mod*inv4%mod;
		ans+=f*c(n,i)%mod*(g[i]-1/*使集合除去交集后非空,保证技能种类不同*/)%mod;
		ans%=mod;
		for(int j=0;j<4;j++) wn[j]=wn[j]*w[j]%mod;
	}
	cout<<ans+1/*可以不放任何技能*/;
}

  

另外有些题目至少的情况不好算,可以拿计算至多的情况f(x)来算,恰好k个的方案数就大于等于k的x的f(x)组合容斥一下。

有些题目有多个属性,每个属性满足的数量都要恰好等于某数,这种的话就容斥套容斥。

即:设题目要求满足要求1的数量为a,满足要求2的数量为2.设f(x,y)表示在至少/至多要x个满足要求1,至少/至多y个满足要求2。

利用这个容斥出g(x,y)表示至少/至多x个满足要求1,恰好y个满足要求2。

然后再利用这个容斥出h(x,y)表示恰好x个满足要求1,恰好y个满足要求2。

然后输出要求的h(x,y)

在实现时一般把这些式子合并成一条式子,一次性求解。

例题:[JSOI2015]染色问题

 

#include<bits/stdc++.h>
using namespace std;
#define mod 1000000007
#define int long long
#define N 410
int fac[N],ifac[N],g[N],kpow[N*N];
int qpow(int aa,int bb)
{
	int res=1;
	int pp=mod;
	aa%=pp;
	while(bb)
	{
		if(bb&1) res*=aa,res%=pp;
		aa*=aa,aa%=pp;
		bb>>=1ll;
	}
	return res;
}
int C(int a,int b)
{
	if(a==0||b==0)return 1;
	return fac[a]*ifac[b]%mod*ifac[a-b]%mod;
}
signed main()
{
   int n,m,c;
   cin>>n>>m>>c;
   fac[0]=ifac[0]=1;
   int t=max(n,max(m,c));
   for(int i=1;i<=t;i++) fac[i]=fac[i-1]*i%mod;
   ifac[t]=qpow(fac[t],mod-2);
   for(int i=t-1;i;i--) ifac[i]=ifac[i+1]*(i+1)%mod;
   for(int k=0;k<=c;k++)
   {
      	kpow[0]=1;
		for(int i=1;i<=n*m;i++) kpow[i]=kpow[i-1]*(k+1)%mod;
		for(int i=0;i<=n;i++)
	   	{
			for(int j=0;j<=m;j++)
			{
				int tag=1;
				if((i+j)&1) tag=-1;
				g[k]+=1ll*tag*C(n,i)*C(m,j)%mod*kpow[(n-i)*(m-j)]%mod+mod;
				g[k]%=mod;
			}
		}
   }
   int ans=0;
   for(int i=c,tag=1;i>=0;i--,tag=-tag)
   {
   		ans=(ans+(tag*g[i]*C(c,i)%mod+mod)%mod)%mod;
   }
   cout<<ans;
}

  

posted @ 2021-08-05 19:48  linzhuohang  阅读(205)  评论(0编辑  收藏  举报