容斥原理学习笔记
定义
一般指将统计问题分解为几个子问题,然后将几个问题的结果分别乘以一个容斥系数求和
注意容斥系数不一定是维恩图的$(-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;
}

浙公网安备 33010602011771号