0x58_数据结构优化 DP
0x58 数据结构优化 DP
状态压缩 DP 和倍增 DP 都是从状态表示入手对 DP 进行的优化。当状态表示和状态转移方程确定后,如何高效地按照公式执行计算也是一个非常值得探讨的问题。从本节开始,我们将从转移入手,探讨 DP 程序实现中的优化手段。
在0x04节的例题“BestCowFences”、0x51节的例题“LCIS”中,我们已经接触到了对于状态转移的优化。这两道题目的动态规划算法在转移时,都需要枚举一个决策,在所有可能情况中取最大或最小值。然而,随着DP阶段的增长,该决策的取值范围的下界不变,上界每次增大1。更一般地,只要这个决策的候选集合只扩大、不缩小,我们就可以仅用一个变量维护最值,不断与新加入候选集合的元素比较,即可直接得到最优决策,O(1)地执行转移。
在更复杂的情况下,我们就要用更加高级的数据结构(而不是仅仅一个变量)维护DP决策的候选集合,以便快速执行插入元素、删除元素、查询最值等操作,把朴素的在取值范围中枚举决策的时间优化为维护数据结构的时间。这就是本节探讨的主要内容。
【例题】Cleaning Shifts POJ2376/POJ3171
有一条很长的白色纸带, 被划分成一个个长度为 1 的网格, 其中第 到第 个网格不慎被染上了黑色墨水。现在有 条贴纸, 第 条贴纸可以覆盖第 到第 个格子, 售价为 。求用若干条贴纸覆盖纸带上的第 到第 个格子, 至少要花费
多少钱。 , 。
设 表示覆盖 需要花费的最小代价。
把所有贴纸按照右端点 递增排序,按顺序扫描这些贴纸。设当前贴纸为 ,价格为 。状态转移方程为:
初值: ,其余为负无穷。目标:
在这个状态转移方程中,需要查询 数组在 上的最小值,同时 数组会不断发生更新。这是一个带有修改的区间最值问题,使用线段树维护 数组即可在 的时间内执行查询、更新操作。对线段树和区间最值问题不熟悉的读者请回顾0x43节。
本题中网格位置的坐标都很小,可以直接在 上建立线段树。当坐标较大时也可以先离散化,再用线段树求解。另外,请读者在程序实现时,注意处理贴纸左、右端点超出 的边界情况。
【例题】The Battle of Chibi HDOJ5542
给定一个长度为 的数列 , 求 有多少个长度为 的严格递增子序列。 , 序列 中的数的绝对值不超过 。因为答案可能很大, 你只需要输出对 取模后的结果。
设 表示 的前 个数构成的以 为结尾的数列中,长度为 的严格递增子序列有多少个。
在上式中, 和 都可看作“阶段”,只会从小往大转移。 是DP的“决策”,有两个限制条件 和 。我们先写出枚举 的朴素程序:
const int mod = 100000007;
memset(f, 0, sizeof(f));
a[0] = -(1 << 30); // -INF
f[0][0] = 1;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
for (int k = 0; k < j; k++)if $(a[k] < a[j])$ $f[i][j] = (f[i][j] + f[i - 1][k])\% mod;$ int ans $= 0$ .
for (int i $= 1$ ;i $< = n$ ;i++) ans $=$ (ans $^+$ f[m][i]) $\%$ mod;在内层循环 和 进行时,可以把外层循环变量 看作定值。我们可以发现,当 增加1时, 的取值范围从 变为 ,也就是只多了 这1个新决策。所以,我们需要维护一个支持如下操作的决策候选集合,其中每个决策可以用一个二元组 来存储:
插入一个新的决策。具体来说,在 增加 1 前,把 加入集合。
给定一个值 ,查询满足 的二元组对应的 的和。
若把二元组的 看作关键码, 看作权值,这就是一个典型的支持插入、查询前缀和的数据结构问题,用平衡树可直接解决。
当然,因为平衡树实现难度较高,我们可以把数列 中的所有数值离散化到 之间,设 表示 离散化后的值。特殊地,我们令 , 。然后在 上建立树状数组,起初所有值为零。
对于插入决策的操作,就把 位置上的值增加 。
对于查询操作,就在树状数组中计算 的前缀和。
综上所述,我们简单地使用树状数组来维护前缀和,就把动态规划的时间复杂度优化到了 。
for (int i = 1; i <= m; i++) {
memset(c, 0, sizeof(c)); // c为存储树状数组数据的数组
add(val(a[0]), f[i-1][0]); // 树状数组add操作
for (int j = 1; j <= n; j++) {
f[i][j] = ask(val(a[j]) - 1); // 树状数组ask操作
add(val(a[j]), f[i-1][j]); // 树状数组add操作
}
}本节提到的三个问题是循序渐进的。这些题目中DP决策的取值范围都可以用简单的不等式来表示。在开头提到的题目中,不等式只有上界变化,且只增大不减小,于是我们采用变量维护法。在第一道例题中,不等式除上界不断增大外,下界变化没有什么规律,此时我们采用了更加灵活的支持区间最值维护的数据结构。在第二道例题中,取值范围有两个限制条件,一个是关于“数组下标”的位置,一个是关于“数列 的数值”的位置,它们实际上是两种“坐标”。DP循环顺序保证了第一个条件的满足。对
于第二个条件,我们在“数列 ”这个“坐标轴”上建立了以 数组中的状态为值的数据结构。
总而言之,无论DP决策的限制条件是多是少,我们都要尽量对其进行分离。多维DP状态在执行内层循环时,把外层循环变量看作定值。状态转移取最优决策时,简单的限制条件用循环顺序处理,复杂的限制条件用数据结构维护,注重二者之间的配合。在更难的题目中,还经常出现数据结构之间的嵌套,以同时满足更多的限制条件。