线性动态规划

《算法设计与分析》期末复习

最优子结构的证明

这篇以 01 背包为例,证明其具有最优子结构性质

指路

导弹拦截

题面

定义状态 \(f(i)\)\(1 ~ i\) 区间内的最大导弹拦截数量

有状态转移

\[f(j) = max \{ f(i) \} + (a[j] <= a[i]) \]

直接赤裸裸的做状态转移,时间复杂度预计在 \(O(n^2)\),空间复杂度在 \(O(n)\)

考虑对于一组序列 $ { a_1, a_2, ..., a_n} $

维护的状态数组中提取出的最长不下降子序列为 $ {1, 1, ..., 2, ..., k} $

实际上对于状态数组中最后一段连续的值相同的序列,选择这段序列中任何一个对应的导弹作为结尾(也就是最后一个打下来的导弹)都合法,但是选择最高的那个必然是最优的

那么可以定义 $ cnt(1 ... num) $ 记录我们的答案,显而易见的是 $ cnt $ 具备一个不怎么严格的单调性

那么对于下标 $ j $,如果 $ a(j) <= a(cnt(num)) $,则 $ f(j) = f(cnt(num)) + 1, cnt(num + 1) = a(j)$

否则二分查找最大的元素 \(a(cnt(k))\) 满足 $ a(cnt(k)) <= a(j) $,把 $ cnt(k) $ 替换为 $ j $;也就是说,我打下来 \(k\) 个导弹,最后一个打下来的导弹不再是之前那个矮的了,而是现在这个高一点的

最后答案就是 \(num\)

第二问涉及到 Dilworth 定理在偏序集上的证明

打鼹鼠

题面

容易证明,起点必然在某一个鼹鼠的出生位

容易想到以下状态转移

\[f(i, j, k) = max \{ f(i + dx, j + dy, k - 1) \} + (a(i, j) == k) \]

空间上太复杂,算法实现上也不做人,必然涉及到记忆化搜索,考虑简化状态转移

实际上,对于题目的需求,坐标本身描述空间的属性意义不大,仅仅规定了从一个状态到另一个状态之间转移要付出的代价

完全可以将二维点抽象成按时间排列的一维的序列,两两之间的转移代价由 $ | x_i - x_j | + | y_i - y_j | $ 唯一确定

那么这题又变成了最长子序列的问题了,只不过不再是由“不下降”或者“不上升”这样的关系规定,而是由 $ | x_i - x_j | + | y_i - y_j | < k_i - k_j $ 这样的关系来规定

那么有状态转移:

\[f(i) = max \{ f(j) \} + (| x_i - x_j | + | y_i - y_j | < k_i - k_j) \]

初始状态需要枚举起点:

\[f(k) = 1 \]

快速求和

题面

容易想到01背包,在做dp之前用 \(O(len^2)\) 求出任意区间组成的数字大小,定义 $ f(j) $ 为容量为 $ j $ 时的最小东西个数

在w(i) 指向的数字序列和 f(j-w(i)) 指向的数字序列没有交叉时,有状态转移:

\[f(j) = min \{ f(j), f(j-w(i)) + 1 \} \]

至于怎么解决这个判定问题,可以朴素求解,在更新 $ f(j) $ 的时候顺带保存对应的 \(index\) 序列,比如 \(123\) 这种,在更新时对而两者各自的序列做一下判断

或者从状态入手,在状态的表示中引入位置信息 $ i $(实际上就是01背包最开始的做法),令 \(f(i, j)\) 表示以 \(i\) 结尾,和为 \(j\) 的最小加法次数

\[f(i, j) = min \{ f(i - k, j - num_{i - k + 1, i}) + 1\} \]

编辑距离

题面

有前面的经验,这个题就很简单了

定义 \(f(i, j)\)\(a_1 ... a_i\)\(b_1 .. b_j\) 相同的所需最少操作

考虑 \(f(i, j)\)\(f(i-1, j)\)转移过来的话,需要删除 \(a_i\),次数 + 1;$ f(i, j) $ 从 \(f(i-1, j-1)\) 转移过来的话,如果不同的话需要做变换,次数 + 1

为啥上面的状态转移看上去没有插入的情况捏,因为删除和插入实际上是一组对偶操作

那么有状态转移

\[f(i, j) = min \{ f(i - 1, j) + 1, f(i, j - 1) + 1, f(i - 1, j - 1) + (a[i] == b[j])\} \]

合唱队形

题面

定义状态 \(f(i, k)\) 表示 \(a_1, ..., a_i, ..., a_k\) 的最长合唱队列长度

\[f(i, k) = max \{ f(i, j) | a_k < a_j \} \]

\(f(i, i)\) 怎么办捏,再提前做一遍最长上升子序列,\(f(i, i)\) 表示最长上升子序列长度

这么做有点蠢

可以直接统计最长上升子序列长度和最长下降子序列长度,枚举中点就好

樱花

题面

典型的混合背包

背包DP

怎么例题也是这个,乐

回文字串

注意到字符串长度不大于 \(1000\),意味着 \(O(n^2)\) 的代价是可以承受的

\(f(i, j)\) 表示把 \(a_i, ..., a_j\) 变成回文串的最小代价

很显然有如下状态转移:

\[f(i, j) = f(i + 1, j - 1) | a_i = a_j \]

\[f(i, j) = min \{f(i + 1, j), f(i, j - 1)\} + 1 | a_i != a_j \]

在实际编写时可能会有这么一个疑惑:究竟是枚举起点还是枚举长度,也即最外层循环到底是什么?

联系前面几道题以及离散数学中学过的 floyd,可以发现决定枚举最外层的因素是阶段而非状态内的变量i或者j

floyd 为什么枚举中转点,因为中转点代表了一个阶段,下一个阶段的计算需要用到这个阶段的计算信息

对于最长子序列问题为什么枚举i,因为当前阶段指 \(a_1, ..., a_i\),下一个阶段指 \(a_1, ..., a_{i+1}\)

回到这题,不难发现实际上要枚举当前计算的区间长度 \(k\)

过河卒

讲实话,根据这个课期中考试的沟槽难度我觉得会考这个

深搜广搜不讲了

\[f(i, j) = f(i - 1, j - 2) + f(i - 2, j - 1) \]

琪露诺

题面

观察数据,发现 $ N <= 2 \times 10 ^ 5$,复杂度基本确定在 \(O(nlogn)\)

首先确认一件事:不走回头路,不然状态的转移会变得有些复杂

对于一个点 \(i\),可以由区间内 \((i-R, i-L)\) 的格子转移过来

显然有状态转移

\[f(i) = max\{f(j)\} + a_i \]

问题是这个max函数,如果用遍历的方式求解显然不可取

考虑维护一个优先队列,每次取队头的时候判断这个东西过期了没,如果过期了就丢掉,否则采纳这个队头

大师

题面

\(f(i, k)\) 为以 \(i\) 结尾,公差为 \(k\) 的选择方案总数

有如下状态转移:

\[f(i, k) = \Sigma (f(j, k) + 1) \text{ if } a_i - a_j = k \]

具体实现的时候 \(k\) 直接由 \(a_i - a_j\) 决定

实际上用 \(f(i, j)\) 表示以 \(a_i\) 作为第一个塔,\(a_i, ..., a_j\) 形成的方案数的状态表示也不是不行;但是要额外记录等差数列的这个差值,实现上有点困难;把约束条件记入状态会更容易实现转移

最长公共子序列

题面

怎么对 \(O(n^2)\) 不友好了 😦

状态没办法简化了,从状态转移入手

感觉没办法入手啊操 😦

怎么变 Hash 了 😦 ?

没做出来,抄一下别人的做法

使用哈希的方法,把任意一组转变成不下降的序列;将同样的映射关系作用到另一组上,这样问题就转化成求一个最长不下降序列的问题了,见导弹拦截

我操 😦 ?

posted @ 2024-05-26 10:31  sysss  阅读(7)  评论(0编辑  收藏  举报