跳至主要內容

计数DP

Mr.Heaboy大约 6 分钟

计数 DP 是一种利用类似 DP 的记忆化搜索方法(与在狭义上的 DP,即最优化问题有一定区别),用于解决计数(以及求和)问题。

计数DP

基础

基本思想

计数问题一般指求一个集合 SS 的大小,在 OI 中,SS 的大小有时会达到 Θ(nn)\Theta(n^n) 甚至 Θ(2n!)\Theta(2^{n!}) 的级别(当然,一般会对某一个固定的数取模),其中 nn 是问题规模,所以我们不能逐一求出 SS 的元素。

如果我们能够将 SS 分成若干无交的子集,那么 SS 的元素个数就等于这些部分的元素个数的和。如果这些子集的计数恰好与原问题类似,那么我们就可以通过类似动态规划的方法来解决。

例题

"例题" 给定一个正整数 nn,求有多少个把 nn 划分成 kk 个正整数的和的方案,位置调换视为不同的划分方案。

需集合 Sn,kS_{n,k} 为形如 (a1,,ak)(a_1, \dots, a_k) 的正整数组组成的集合,其中 a1++ak=na_1 + \dots + a_k = n。如果 aka_k 固定,则有如下推导:因为 a1+a2++ak1+ak=na_1 + a_2 + \dots + a_{k-1} + a_k = n,所以 a1+a2++ak1=naka_1 + a_2 + \dots + a_{k-1} = n - a_k。根据 Sn,kS_{n,k} 的定义,(a1,a2,,ak1)Snak,k1(a_1, a_2, \dots, a_{k-1}) \in S_{n - a_k, k - 1}

由于 a1,a2,,aka_1, a_2, \dots, a_k 是正整数,所以 aka_k 的取值范围是 [1,nk+1]Z[1, n - k + 1] \cap \mathbb Z。因此,Sn,kS_{n,k} 可以按照 aka_k 被划分,分成 nk+1n - k + 1 个子集,其中当 ak=ia_k = i 时,这个子集为:

{(L,i)LSni,k1}. \{(L, i) \mid L \in S_{n-i,k-1}\}.

这个子集的元素个数显然等于 Sni,k1S_{n-i,k-1},由于 ii 的不同,这些子集两两无交。所以:

Sn,k=i=1nk+1Sni,k1. |S_{n,k}| = \sum_{i=1}^{n-k+1} |S_{n-i,k-1}|.

这样我们就可以使用类似 DP 的方法处理它:设 fn,kf_{n,k}Sn,k|S_{n,k}|,则有状态转移方程:

fn,k=i=1nk+1fni,k1. f_{n,k} = \sum_{i=1}^{n-k+1} f_{n-i,k-1}.

这样就可以使用 DP 的方法求解了。

与最优化 DP 的异同

可以发现,计数 DP 和最优化 DP 都是在一个范围 Ω\Omega 内求一个值(大小值、最优值),这个值通过将 Ω\Omega 中的所有元素做一次处理,再对处理值做一次整合得到。

例如,对于 0-1 背包问题,Ω\Omega 中的元素为背包内的所有物品组成的集合,对于 Ω\Omega 中的一个方案 SS,我们对 SS 做一次处理,处理得到的结果 w(S)w(S)SS 中物品的总价值,对所有得到的处理值,我们取最大值,得到问题的答案。

对于计数问题,Ω\Omega 中的元素为要计算元素个数的集合 SS,它的处理是把所有的 SS 中元素变为 11,然后将这些 11 通过加法的方式汇总起来,因为每一个 SS 中元素都对应一个 11,所以这样得到的值就是 SS 中元素个数。

当汇总操作为最大/最小值时,我们可以将 Ω\Omega 分成任意若干个部分,只需这些部分的并为 Ω\Omega 即可,无需无交的条件。而计数问题由于不满足这个条件,所以我们需要将 Ω\Omega 分成若干个部分,这些部分两两无交,这就是与最优化 DP 的区别。

例题

"例题" 给定一个正整数 nn,求有多少个把 nn 划分成任意多个正整数的和的方案,位置调换视为 相同 的划分方案。

解法 1

需要计算的集合的元素为满足其和为 nn 的正整数多重集。但是这样显然不好推。

若一个多重集 TT 只包含 M\le M 的正整数,且 TT 中所有元素的和为 nn,则称 TSn,MT \in S_{n, M}。考虑 MM 出现的个数。可能为 k[0,nM]Zk \in \left[0, \left\lfloor \dfrac nM \right\rfloor\right] \cap \mathbb Z。于是它可以被转移到 SnkM,M1S_{n - kM, M - 1}。求和一下即可。复杂度是 Θ(n2logn)\Theta(n^2 \log n)log\log 来自于 kk 的范围导致的调和级数)。

但是这样还不够优秀。考虑下面所示的一个例子:

f8,3=f8,2+f5,2+f2,2f9,3=f9,2+f6,2+f3,2+f0,2f10,3=f10,2+f7,2+f4,2+f1,2f11,3=f11,2+f8,2+f5,2+f2,2f12,3=f12,2+f9,2+f6,2+f3,2+f0,2f13,3=f13,2+f10,2+f7,2+f4,2+f1,2 \begin{aligned} f_{8, 3} &= {\color{red}f_{8, 2} + f_{5, 2} + f_{2, 2}} \\ f_{9, 3} &= {\color{blue}f_{9, 2} + f_{6, 2} + f_{3, 2} + f_{0, 2}} \\ f_{10, 3} &= {\color{green}f_{10, 2} + f_{7, 2} + f_{4, 2} + f_{1, 2}}\\ f_{11, 3} &= f_{11, 2} + {\color{red}f_{8, 2} + f_{5, 2} + f_{2, 2}}\\ f_{12, 3} &= f_{12, 2} + {\color{blue}f_{9, 2} + f_{6, 2} + f_{3, 2} + f_{0, 2}}\\ f_{13, 3} &= f_{13, 2} + {\color{green}f_{10, 2} + f_{7, 2} + f_{4, 2} + f_{1, 2}}\\ \end{aligned}

等量代换得 f11,3=f11,2+f8,3f_{11, 3} = f_{11, 2} + f_{8, 3}f12,3=f12,2+f9,3f_{12, 3} = f_{12, 2} + f_{9, 3}f13,3=f13,2+f10,3f_{13, 3} = f_{13, 2} + f_{10, 3}。同理我们可以得到一个通用的状态转移方程:

fn,M=fn,M1+{fnM,MnM,0otherwise. f_{n, M} = f_{n, M - 1} + \begin{cases} f_{n - M, M} & n \ge M, \\ 0 & \text{otherwise}. \end{cases}

此时,时间复杂度为 Θ(n2)\Theta(n^2)

解法 2

考虑到某一个正整数组成的多重集 TT 必然可以通过「将 TT 中每一个元素自增」、「在 TT 中加一个值为 11 的元素」两个操作得到,并且不同的操作序列得到的结果是不同的。

这样对 TT 的转移可以变为对操作序列的转移。考虑将 nn 划分成 mm 个数的操作序列(所有的这些操作序列记作 Bn,mB_{n,m})中的最后一次操作,如果是 11 操作,那么不会增加数,但是 T\sum T 增加了 mm。为了使最终的 T=n\sum T = n,原来的 TT(记作 TT')的和需要为 nmn-m。所以 Bn,mBnm,mB_{n,m} \to B_{n-m,m};如果是 22 操作,那么会增加一个数,T\sum T 增加了 11。所以 Bn,mBn1,m1B_{n,m} \to B_{n-1,m-1}

这样做的时间复杂度依旧是 Θ(n2)\Theta(n^2)

解法 3

考虑将 TT 分为大于 n\sqrt n 的部分 T1T_1 和小于等于 n\sqrt n 的部分 T2T_2T2T_2 可以使用解法 1 求出,而 T1T_1 的数量可以通过略微修改解法 2 求出:考虑将两个操作变为「将 T1T_1 中每一个元素自增」、「在 T1T_1 中加一个值为 n+1\lfloor \sqrt n \rfloor + 1 的元素」。容易列出状态转移方程。

nn 拆为 AABB 两部分。枚举其中一个即可得出另一个。将满足 T1=A\sum T_1 = AT1T_1 个数和 T2=B\sum T_2 = BT2T_2 个数求出,乘起来,对所有的 AA 求和便是最终结果。

由于在计算 T1T_1 个数的过程中,MnM \le \sqrt n,所以我们利用解法 1 计算 T1T_1 的时间复杂度为 Θ(n3/2)\Theta(n^{3/2})。同样地,由于在计算 T2T_2 个数的过程中,T2T2nnn=n|T_2| \le \dfrac{\sum T_2}{\sqrt n} \le \dfrac{n}{\sqrt n} = \sqrt n,所以我们利用解法 2 计算 T2T_2 的时间复杂度也是 Θ(n3/2)\Theta(n^{3/2})。所以总时间复杂度为 Θ(n3/2)\Theta(n^{3/2})