容斥技术(长期更新)

容斥的最直接的想法就是给算重了的东西填上系数,使得不合法的东西都算了0次,然后合法的东西都算了c次(c为常数,c=1当然最好)。

要证明一种容斥是对的,就考察任意一个对象,验证其计数次数是否符合要求即可。

普通容斥

对于一个集合 S 的一部分子集构成的簇 P 有:

|TPT|=QP(1)|Q|1|TQT|

证:

设元素 xk(k0)S 的子集中被包含,则它对左式的贡献为 1。现在来看它对右式的贡献,为:

i=1k(1)i1(ki)=(1)×i=1k(1)i(ki)=1(1)×i=0k(1)i(ki)=1+(11)k=1

i 相当于在枚举 |Q| 的大小。

所以原式得证。

最为简单的容斥就是正难则反的思想,用不考虑任何限制的减去有限制的,有的时候有限制的可以直接算了就不用继续想了。

二项式反演似乎是很常用的容斥技巧,但是已经把它放在反演里面了,就不单独说了。毕竟二项式反演已经告诉了我们应该使用哪种系数,所以是简单的。

各种反演也是常用的容斥手段,相当于已经知道系数的容斥。

介绍一种将容斥与生成函数相结合的思想,这使得容斥的含金量还在上升。

以下内容纯属口胡,可能不太正确,不太完整。


首先我们可以对要计数的对象搞一个生成函数G出来,然后又记容斥系数的生成函数为F

于是最终就是F通过运算得到G,然后就可以尝试反解出F,就得到了容斥系数。

这样做应该就不用猜测容斥系数了(?)。

更一般的凑容斥系数的方法

考虑要计数恰好k种集合覆盖的元素贡献为fk大小s的集合的容斥系数为gs

那么就有1ik(ki)gi=fk

g0=0,那么0ik(ki)gi=fk

反演一下得到gk=0ik(1)ki(ki)fi

Min-Max 容斥

式子:

maxS=TS(1)|T|1minTmaxkthS=TS(1)|T|k(|T|1k1)minT

以上两个式子把 min,max 对换也是成立的,即:

minS=TS(1)|T|1maxTminkthS=TS(1)|T|k(|T|1k1)maxT

等着补证明。

反射容斥

用于格路计数问题,可以在转移与格路计数类似的DP中见到,然后直接用数学方法优化。

首先容易得到(x0,y0)关于y=x+b对称得到(y0b,x0+b)

以及(0,0)走到(n,m)的方案数为(n+mn)

先来考虑一下Catalan数的格路计数的推导方式解决一个类似的问题,这里我们不能经过y=x+1,从(0,0)走到(n,m)

我们对每一种经过了y=x+1的非法路线沿y=x+1翻折,每一种非法路线的终点都是(m1,n+1),可以简单证明(0,0)走到(m1,n+1)的路线一一对应着一条非法路线。于是Cn=(n+mn)(n+mn+1)

于是不能经过一条直线的格路计数问题已经解决了。现在来考虑不能经过两条直线的情况。解决了两条直线的情况,我们就已经知道了格路计数中不能经过k条直线的全部,因为只需要取距离y=x最近的两条直线即可。

两条直线在同侧的情况已经解决了,这里考虑两条直线y1=x+ly2=x+rl<0<r,当然要异侧)。

我们记L表示经过了y1一次,R表示经过了y2一次,若连续多次经过同一条直线,我们只在其第一次经过时记录一次。于是将当前折线经过的直线的字母写下来,就是S=LRLR或者RLRL或者

我们记f(S)表示S作为一个子序列出现过的所有路径的方案数。

那么有结论:从(0,0)走到(n,m)的方案数ans=f()+k1,k=|s|(1)k(f(LRLR)+f(RLRL))

证明是这样的,考虑一个串S=LRLR(不失一般性,不妨以L开头)被计数了几次(应该是0次)。空集贡献了1。是S的子序列的串就是S的每个前缀以及S去掉第一个字符后的串的每个前缀。前一种的贡献是k=1|S|(1)k,后一种的贡献为k=1|S|1(1)k=(1)×k=2|S|(1)k,两个加起来就是1,再算上空集的贡献就是0。于是对于S的都只算了0次,所以是对的。

然后又怎么算呢?发现f(S)是好算的,设(n,m)经过S中的翻折(类似反射?)到了(n,m),那么f(S)=(n+mn)

先把ans的前几项写开:

ans=(n+mn)(n+mn+l)(n+mn+r)+(n+mn+lr)+(n+mnl+r)(n+mn+2lr)(n+mnl+2r)+(n+mn+2l2r)

大力观察可以发现,我们不要原来的那个求和号,直接拆开,然后每四个为一组来看,第一个与第三个配对,第二个与第四个配对,可以得到更容易计算的柿子:

ans=kZ(n+mnk(rl))(n+mnk(rl)+r)

注意到随着k增大,下指标到负数就全为0了,所以可以预处理O(nrl)个组合数,然后O(n)去算。

其实最终的难点不在于使用反射容斥,而是中间的转化。

例题

P3266 [JLOI2015] 骗我呢

简要题意:n×m的格子,每个格子中有0Xi,jm。满足Xi,j<Xi,j+1Xi,j<Xi1,j+1。现在来填这个格子,有多少种方案数?

Sol:

首先来想朴素DP。发现一行中数字两两互不相同,一行有m个数要填,而值域为[0,m]一共m+1个数。这个性质很好,可以设DP状态为fi,j表示填到了第i行,第i行没有填j的方案数。

然后手推一下,发现上一行要是没有填>j+1的数,第i行就一定会填j。于是转移就是fi,j=k=0j+1fi1,k

再化一步,注意到有点像前缀和的形式,可以化出fi,j=fi,j1+fi1,j+1j0),j=0时,fi,j=fi1,j+fi1,j+1i=1时,fi,j=1

然后这是二维的DP,发现形式很像在格路上统计路径数量。于是把图手画一下。注意j=0处有格外的边。由于有斜着的边,于是把每行平移一点,总之可以转化到从(0,0)走到(n,n+m+1),不经过y1=x1y2=x+m+2的方案数,上反射容斥就做完了。

发现反射容斥可以把看起来像格路计数的DP柿子直接O(n)算。

参考:大佬的容斥博客

posted @   RandomShuffle  阅读(61)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示