浅析容斥和DP综合运用

浅析容斥和DP综合运用

前言

众所周知在数数题中有一种很重要的计数方法——容斥。但是容斥有一个很大的缺陷:枚举子集的复杂度过高。所以对于数据规模较大的情况会很乏力,那么我们就只能引入容斥DP。

复习一下容斥

什么情况下用容斥?容斥能干什么?

容斥的基本功能就是当你知道任意个指定集合的交集,你就能推出这些集合的并集。

形式化的来说,就是:

\[\left|\bigcup_{i=1}^{n} A_{i}\right|=\sum_{i=1}^{n}\left|A_{i}\right|-\sum_{1 \leq i<j \leq n}\left|A_{i} \cap A_{j}\right|+\sum_{1 \leq i<j<k \leq n}\left|A_{i} \cap A_{j} \cap A_{k}\right|-\cdots+(-1)^{n-1}\left|A_{1} \cap \cdots \cap A_{n}\right| \]

只使用容斥朴素算法

如果我们只会容斥,我们该怎么做?很显然根据上面的公式,我们需要枚举任意集合的组合方式,然后统计他们的答案,将他们加入答案。

比如说在【线上训练 5】乘方中,当我们枚举出子集,我们就很容易求出子集的大小。

【线上训练3】数个数,当我们枚举出了子集,我们也能统计出子集的大小

我们通过以上两道题,总结出了这种容斥题的一个特点:都是求集合的并集,同时你可以通过一些方式求得集合的交集。

使用DP进行优化

我们思考一下就会发现,上面两道题的复杂度瓶颈都在于需要\(2^k\)的枚举出所有的子集再进行DP。那我们就可以考虑进行DP。因为对于一个子集,添加一个元素,就会导致他贡献的符号取反。

一般DP状态都是\(dp[i][j]\),其中\(i\)代表前\(i\)个集合中的元素。而\(j\)一般代表一个决定交集大小的值。而对于\(j\)值相同的所有状态(子集),在它们之后再添加一个元素,对答案增加的贡献都一样。

举个例子:

【线上训练3】数个数中,如果往一个子集内加入新的元素,子集的大小就会增加\((字符集)^{(加入的区间位置-上一个区间位置)}\)。所以我们记录的\(j\)就是上一个区间的位置。
而在【线上训练 5】乘方中,如果往一个子集内加入新的元素,子集的大小就会变成\(lcm(j,N_i)\)。所以\(j\)记录的就是选择的子集的\(lcm\)

对于前一道题而言,因为决定所选子集的大小是子集中元素的间隔距离。所以我们需要一边\(dp\)选择元素,一边把每一次往子集里添加元素增加的贡献累加进入最终答案。

而对于后一道题而言,因为决定所选子集的大小是子集中元素的\(lcm\),因为这是一个数,而且这个数和前面说的转移答案所需要的\(j\)是同一个数,所以我们可以只在\(dp\)数组里记录容斥系数的和,等到最后再来统计答案。

posted @ 2019-10-23 15:15  GavinZheng  阅读(700)  评论(0编辑  收藏  举报