概念
区间动态规划(Interval Dynamic Programming)是动态规划的一个分支,它在处理一些与区间相关的最优解问题上非常有效。以下从基本概念、解题步骤、经典例题、优缺点等方面为你详细介绍:
基本概念:区间 DP 的核心思想是将问题划分为不同长度的区间,通过求解小区间的最优解,逐步合并得到大区间的最优解。它通常用于解决一类可以将问题分解为若干相互关联的子区间问题,并且这些子区间的最优解可以通过更小的子区间的最优解推导出来的问题。
解题步骤
定义状态:通常用 (dp[i][j]) 表示区间 ([i, j]) 上的某种最优值,比如 (dp[i][j]) 可以表示从下标 (i) 到 (j) 的元素进行某种操作所得到的最大收益、最小花费等。
状态转移方程:这是区间 DP 的关键。它描述了如何从较小的区间的最优解得到较大区间的最优解。例如,对于一个表达式求值问题,可能有 (dp[i][j] = max{dp[i][k] + dp[k + 1][j] + text{合并操作}(i, k, j)}),其中 (i leq k < j),即通过枚举区间 ([i, j]) 内的分割点 (k),将区间 ([i, j]) 拆分成两个子区间 ([i, k]) 和 ([k + 1, j]),然后根据具体问题的要求进行合并操作。
初始化:一般需要初始化长度为 1 的区间,即 (dp[i][i]) 的值,这通常根据问题的具体情况来确定。例如在某些问题中, (dp[i][i]) 可能表示单个元素的某种属性值。
计算顺序:按照区间长度从小到大的顺序进行计算。先计算长度为 2 的区间的 (dp) 值,再计算长度为 3 的区间,以此类推,直到计算出长度为 (n)(问题规模)的区间的 (dp) 值。这样可以保证在计算较大区间时,其所依赖的小区间的最优解已经计算出来。
引例(石子合并):
分析:
有 (N) 堆排成一排的石子,每堆石子质量用整数表示,要求将这 (N) 堆石子合并成一堆。合并规则是每次只能合并相邻的两堆,合并代价为两堆石子质量之和,且合并顺序不同,总代价不同,目标是找出总代价最小的合并方法并输出最小代价。
状态定义:使用二维数组 (dp[i][j]) 表示将第 (i) 堆石子到第 (j) 堆石子合并成一堆的最小代价。其中 (i) 和 (j) 分别表示区间的起始和结束位置,(1<=i<= j<= N)。
状态转移方程:通过枚举区间 ([i, j]) 内的分割点 (k)((i<=k < j)),将区间 ([i, j]) 拆分成 ([i, k]) 和 ([k + 1, j]) 两个子区间。状态转移方程为 (dp[i][j]=min{dp[i][k]+dp[k + 1][j]+sum_{s = i~j} a[s]}) 。其中 (sum_{s = i~j}a[s]) 是将第 (i) 堆到第 (j) 堆石子合并成一堆时这一次合并的代价,(a[s]) 表示第 (s) 堆石子的质量。
初始化:当 (i = j) 时,即只有一堆石子,不需要合并,所以 (dp[i][i]=0) 。
(注意,这里的是相邻的两个石子合并,不是任意两个石子合并,如果是任意两个就变成霍夫曼树题了)
思路
伪代码:
习题1(环形区间dp)环形石子合并:
分析:
这是一个环形石子合并问题,是在直线石子合并基础上的拓展,属于区间动态规划范畴。以下从题目条件、解题关键、解题思路等方面介绍:
有 (n) 堆石子绕圆形操场排放,需将它们有序合并成一堆。
每次只能合并相邻的两堆,合并新堆的石子数作为此次合并的得分。
要求编写程序,读入堆数 (n) 及每堆石子数,计算出 (n - 1) 次合并得分总和最大与最小的方案对应的得分。
1先将环形石子堆转化为直线石子堆(复制一份接到原序列后)。
2计算区间长度为 2 的 (maxdp) 和 (mindp) 值,即相邻两堆石子合并的得分情况。
3按照区间长度从小到大的顺序计算 (maxdp) 和 (mindp) 值,例如从长度为 3 的区间开始,依次计算到长度为 (n) 的区间。
4因为环形的特性,在长度为 (n) 的区间中,找出所有满足条件的 (maxdp[i][i + n - 1]) 中的最大值作为合并得分总和的最大值,找出所有 (mindp[i][i + n - 1]) 中的最小值作为合并得分总和的最小值。
破环为链有2个做法:
我们已知的一般区间dp的时间复杂度是n^3,枚举缺口也是n种,这样的时间复杂度就是n^4,太高了,再看看第二种,我们只需要处理2n的一个序列就好了,之后枚举长度为n的答案,这样的时间复杂度只有8*n^3.
伪代码:
习题2(环形区间dp)能量链条:
分析:
这是一道基于区间动态规划的问题,关键在于找出能量珠聚合的最优顺序以获取最大总能量。以下从题目条件、解题思路、关键步骤等方面进行分析:
能量珠特性:在 Mars 星球上,每个 Mars 人有一串含 (N) 颗能量珠的项链。能量珠有头标记和尾标记,且相邻珠子前一颗的尾标记等于后一颗的头标记,这是珠子能够聚合的前提条件。
聚合规则与能量计算:当两颗相邻珠子聚合时,若前一颗头标记为 (m),尾标记为 (r),后一颗头标记为 (r),尾标记为 (n),则聚合释放能量为 (m* r* n)(Mars 单位),新珠子头标记为 (m),尾标记为 (n)。
目标:设计聚合顺序,使项链释放的总能量最大。
破环为链:将长度为 (N) 的环形能量珠序列复制一份接到原序列后面,形成长度为 (2N) 的直线序列,以便在直线序列上进行区间 DP 计算来覆盖环形结构的所有聚合情况。
计算区间 DP 值:按照区间长度从小到大的顺序计算 (dp) 值。先计算长度为 2 的区间(即两颗相邻能量珠聚合的情况),再逐步计算长度更大的区间,直至长度为 (N) 的区间。
获取结果:在长度为 (N) 的区间中,找出所有 (dp[i][i + N - 1]) 中的最大值,即为这串项链能释放的最大总能量。
伪代码:
习题3(高精度+区间dp)凸多边形的划分:
分析:
给定有 (N) 个顶点且顶点有权值的凸多边形,目标是找出一种将其划分为 (N - 2) 个互不相交三角形的方式,使得这些三角形顶点权值乘积之和最小。由于划分方式众多,直接枚举所有情况时间复杂度高,所以适合用动态规划求解。
合理枚举中间顶点:在状态转移时,准确枚举中间顶点 (k) 来划分凸多边形,从而得到子问题的最优解并推导出原问题的最优解。
划分示意图:
思路:
伪代码:
因为这个题目的数据量过大,要使用高精度算法:
下面实现高精度加法乘法,和大数字的比较
代码主体:
习题4(树+区间dp)加分二叉树:
分析:
二叉树遍历特征:给定二叉树的中序遍历为 (1, 2, 3, cdots, n) ,这一条件限制了二叉树节点的相对位置关系。因为中序遍历的特点是左 - 根 - 右,所以若确定某节点 (k) 为根节点,那么其左子树节点编号范围是 (1) 到 (k - 1) ,右子树节点编号范围是 (k + 1) 到 (n) ,这为后续动态规划中对问题的划分提供了依据。
节点分数与加分规则:每个节点都有一个正整数分数 (d_i) 。二叉树及其子树的加分计算规则为:子树的左子树加分 × 右子树加分 + 子树的根的分数,并且规定空子树加分是 (1) ,叶子节点的加分就是其自身分数。此规则明确了不同结构二叉树的加分计算方式,是动态规划中状态转移的基础。
计算 (dp) 值:按照区间长度从小到大的顺序依次计算 (dp) 值。先计算长度为 (1) 的区间(即单个节点情况),然后计算长度为 (2) 的区间(两个节点构成简单二叉树情况),以此类推,直到计算出长度为 (n) 的区间(整个二叉树)的 (dp) 值。最终 (dp[1][n]) 就是所求二叉树的最高加分。
记录根节点信息:在计算 (dp) 值的过程中,同时记录每个区间 ([i, j]) 对应的使得加分最高的根节点 (root[i][j]) 。这一步是为了后续能够通过回溯构建出最优二叉树,进而得到其前序遍历。
回溯求前序遍历:从记录的 (root[1][n]) 开始回溯。先输出根节点编号,然后对左子树(对应区间 ([1, root[1][n] - 1]) )和右子树(对应区间 ([root[1][n] + 1, n]) )递归进行同样的操作,从而得到二叉树的前序遍历序列。
伪代码:
g是i到j元素的根
f是dp数组
先实现一个dfs前序遍历的函数
然后是代码主体
习题5(二维区间dp)棋盘分割:
分析:
棋盘与分割规则:给定一个 (8×8) 的棋盘,每次分割需割下一块矩形棋盘且使剩余部分也是矩形,且只能沿棋盘格子的边进行,经过 ((n - 1)) 次分割后得到 (n) 块矩形棋盘。
分值与目标:棋盘上每格有一个分值,矩形棋盘总分是其所含各格分值之和。要求计算出分割成 (n) 块矩形棋盘后,各矩形棋盘总分均方差的最小值。均方差的化简
预处理棋盘,计算出每个子矩形棋盘的总分,可通过前缀和的方式快速计算。
按照分割块数 (m) 从小到大、矩形棋盘大小从小到大的顺序计算 (dp) 值。
最终 (dp[1][1][8][8][n]) 即为将整个 (8×8) 棋盘分割成 (n) 块时均方差的最小值。
思路: