基础区间dp

一、区间dp的基本内容

区间dp可以视作线性dp的一个分支,之所以把它单独列出来是因为区间dp的解法比较特殊,同时也比较固定。虽然容易造成初见杀,但是反而容易掌握。区间dp与其他线性dp不同的地方在于它的状态是以序列上的一个区间来表示的,而且大区间的答案可以从小区间的答案得到。下面是区间dp的基本思路:

设计状态:至少要有dp[l][r]两维分别表示区间的左端点和右端点。

状态转移:一般通过枚举区间[l,r]之间的点k把[l,r]分成[l,k]和[k+1,r],然后用dp[l][k]和dp[k+1][r]推出dp[l][r]。

边界条件:区间l==r时dp[l][r]可以从a[l]得出。(或者为初值)

区间dp的枚举顺序往往很有趣。根据dp顺序的原则,执行赋值时等号右边的dp值一定要是已经算出来了的结果。所以如果只是简单地从1~n分别枚举l,r,k就会出错,这里给出两种常用的枚举方法:

  1. 首先枚举长度len:1~n;然后枚举起点l:1~n;这样可以算出终点r=l+len-1;最后枚举断点k:l~r。注意终点r不能大于序列总长度n。
  2. 首先倒序枚举起点l:n~1;然后枚举终点r:1~n;最后枚举断点k:l~r。

如果两种枚举都不喜欢,那么也可以用记忆化搜索。

 

二、普通序列上的区间dp

【例1 合并石子(一)】

在一个操场上摆放着一排 N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的 2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。求将所有石子合并成一堆的最小得分。

 

分析:按照区间dp的基本套路,设dp[l][r]表示将区间[l,r]的石子合并成一堆的最小得分。枚举断点k进行转移,如果[l,r]被分成了[l,k]和[k+1,r]两个区间,那总区间的得分肯定包含了两个区间本来的得分(即dp[l][k]和dp[k+1][r]),还有把这两个区间的两堆石子合并成一堆时得到的得分(即a[l]+a[l+1]+...+a[r])。得到的dp方程是dp[l][r]=min{dp[l][k]+dp[k+1][r]+}。如果使用前缀和优化,可以记s[i]= a[1]+a[2]+...+a[i],那么=s[r]-s[l-1]。dp方程可以被优化为dp[l][r]= min{dp[l][k]+dp[k+1][r]+s[r]-s[l-1]},边界为dp[i][i]=a[i]。可以在n3效率下通过此题。

 

【例2 加分二叉树】

设一个 n 个节点的二叉树 tree 的中序遍历为(1,2,3,…,n),其中数字 1,2, 3,…,n 为节点编号。每个节点都有一个分数(均为正整数),记第 i 个节点的分数为 di​,tree 及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree 本身)的加分计算方法如下:

subtree 的左子树的加分 × subtree 的右子树的加分 +subtree 的根的分数。

若某个子树为空,规定其加分为 1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。

试求一棵符合中序遍历为 (1,2,3,…,n) 且加分最高的二叉树 tree。要求输出

tree 的最高加分。

tree 的前序遍历。

 

分析:这道题容易让人以为是树形dp(尤其对于还没学树形dp的人而言)。但是仔细深究会发现题目只给了中序遍历,而这是一个序列,并且可以指定一个区间形成树,让区间中某个点是根节点,根节点左边就是左子树,右边就是右子树。所以我们考虑区间dp的模型:设dp[l][r]为把区间[l,r]的点构造成一棵二叉树能得到的最高加分,dp方程则为dp[l][r]=max{dp[l][k-1]*dp[k+1][r]+a[k]},其中k属于[l,r]。

细心的同学可能发现,这个dp方程会访问到dp[l][l-1]和dp[r+1][r]两个东西。这两个区间里l>r,这是什么意思呢?原来二叉树不一定非要有两个儿子,可能有一个子树是空的。dp[l][l-1]表示空左子树,dp[r+1][r]表示空右子树。这也启发我们根据题意设置边界条件为:dp[i+1][i]=1以及dp[i][i]=a[i]。

还有一个问题,如何输出中序遍历?实际上我们只要确定每一个区间[l,r]以谁为根节点是加分最大。上边的dp方程枚举了根节点k,总会有一个k取到了最终的max对吧?那这个k就是我们要找的最优根节点,可以记p[l][r]=k。求前序遍历时递归输出就好了。

 

三、环上的区间dp。

【例1 合并石子(二)】(NOI1999)

与初级相同,唯一的区别是这次我们把N堆石子摆成了一个环,环上任意两堆相邻的石子都可以合并。

 

分析:虽然说摆成了一个环,但是并不是任意两堆石子都要合并:我们只会合并(n-1)次,所以说总会存在两堆相邻的石子它们始终没被合并,可以从这里把环断开。只不过我们不知道具体该断哪里,需要枚举n次跑n次区间dp。这样的时间复杂度是n4

优化这个时间复杂度,仔细考虑这n个区间分别是什么。如果说给出的数据是1 2 3 4 5,这5次分别跑了1 2 3 4 5, 2 3 4 5 1, 3 4 5 1 2, 4 5 1 2 3, 5 1 2 3 4这5个区间。如果我们把环拆开并复制一遍就是1 2 3 4 5 1 2 3 4 5。可以发现之前5个区间现在全是这个大区间的子区间。如果对这个大区间执行区间dp,这个大区间的每一个子区间的dp值都可以得到。其中也包括了我们需要的那5个。时间复杂度为n3

    总结一下,对于所有在环上执行的区间dp,需要把环拆开并复制一倍,答案就是每个长度为n的区间的dp值的最值。

    合并石子的拓展很多,还有n2级别的合并石子(三)和nlogn级别的合并石子(四)。立志于省选或NOI的同学可以查找“四边形不等式”和“GarsiaWachs算法”尝试解决上述问题。

 

【例2 能量项链】

多个能量珠连成一个环形的能量项链。能量珠有头尾标记且前一颗能量珠的尾标记数值上总等于后一颗的头标记,如果前一颗能量珠的头标记为m,尾标记为r,后一颗能量珠的头标记为r,尾标记为n,则聚合后释放的能量为m*r*n,新产生的珠子的头标记为m,尾标记为n。现求一合并顺序使释放出的总能量最大。

 

    分析:根据这道题的输入数据,可以设a[i]表示的是第i个珠子的头标记,也是第(i-1)个珠子的尾标记。首先仍然是破环为链,但是这个题比较特殊的一点是最后一个珠子的尾部标记不能被忽略,所以复制之后还要加上一句a[2*n+1]=a[1]。

    设dp[l][r]表示把第l个珠子到第r个珠子合并成一个的最大能量,那么在这种情况下a[l]是没有被消去的。所以说枚举断点k转移时,dp[k+1][k]里a[k+1]也没有被消去,dp方程应该是dp[l][r]=max{dp[l][k]+dp[k+1][r]+a[l]*a[r+1]*a[k+1]}。通过对2n长度的链做dp就可以了。

posted @ 2022-03-18 14:44  chyuhoin  阅读(130)  评论(0)    收藏  举报