浅谈动态规划

浅谈动态规划

动态规划问题经常出现在各种算法竞赛和面试中。需要说明的问题是,这里的动态规划比较广义,也包括一部分组合计数问题。最近感觉对这类问题有了一些认识和心得。从认识方面上,我试图找到一种对动态规划问题的通用解释。在心得上,尽管限于经验和练习量有限,不是次次都能解出,我暂时探索出了一套比较好的思路。

在这里我们以一个经典问题为例:

POJ 1163

给出一个 n 行 n 列的三角形,形如:

      7
    3   8
  8   1   0
2   7   4   4

从顶端出发,每次可以选择向左或者向右走,求一条路径,从顶走到底,使路径上的和最大。

问题的解法大家都知道:设 ( DP_{i,j} ) 表示走到第( i )行,第( j )列的最优解,( Tri_{i, j} )表示第( i )行,第( j )列的数字,则有 [ DP_{i,j} = max(DP_{i-1,j}, DP_{i,j-1}) + Tri_{i,j} ] 问题的解为 [ max{DP_{n,i}} ] 暂时忘记结论,我的想法如下:

首先,考虑状态的编码。我们知道,搜索问题和动态规划问题,都需要设计状态。显然同一个状态有不同的编码方法。对于这个问题的一个状态:

{
  访问序列 = 7,8,1
  当前位置 = (3,2)
  当前和 = 16
}

我们有若干方法编码:

  • 记录决策,L 代表左,R 代表右:RL => 16,使用二进制可以编码成:( (10)_{2} )
  • 记录当前位置:第 3 行第 2 列:(3, 2) => 16

直接搜索产生可行解集的过程如下:

{} ->
{L,R} ->
{LL, LR, RL, RR} ->
{LLL, LLR, LRL, LRR, RLL, RLR, RRL, RRR}

问题的复杂度是指数级。

我们考虑动态规划的性质:最优子结构和无后效性。所以对于当前问题,我们只关心当前位置,而不关心历史信息。编码方法 RL 保留了历史信息,而 (3, 2) 没有。由于信息损失,多个不同的状态,可能有相同的编码。我们进行演算:

{(1,1) => {}} ->
{(2,1) => {L}, (2,2) => {R}} ->
{(3,1) => {LL}, (3,2) => {LR, RL}, (3,3) => {RR} } ->
{(4,1) => {LLL}, (4,2) => {LLR, LRL, RLL, }, (4,3) => {LRR, RLR, RRL}, (4,4)=>{RRR}}

根据最优子结构,位置(4, 2) 和 (4, 3) 的最优解,只可能由位置 (3, 2) 的最优解产生。具体来说,因为 7 + 8 + 1 > 7 + 3 + 1,所以 7 + 8 + 1 + X > 7 + 3 + 1 + X。于是对于所有编码同为 (3, 2) 的方案,我们只保留最优的,这样做相当于实现了搜索的剪枝,计算步骤如下:

{} ->
{(2,1) => {L}, (2,2) => {R}} ->
{(3,1) => {LL}, (3,2) => {LR, RL}, (3,3) => {RR} } 剪枝 ->
{(3,1) => {LL}, (3,2) => {RL}, (3,3) => {RR} } ->
{(4,1) => {LLL}, (4,2) => {LLR, RLL}, (4,3) => {RLR, RRL}, (4,4)=>{RRR}} 剪枝 ->
{(4,1) => {LLL}, (4,2) => {LLR}, (4,3) => {RLR}, (4,4)=>{RRR}}

于是,问题的状态空间,从指数级映射到了 ( O(n^{2}) ),且没有丢解。

于是,我认为动态规划的本质是在搜索法的前提下,对状态重新编码,将编码相同的状态组织起来,并利用最优子结构的性质剪枝(或加法、乘法原理计数)。而解决动态规划的核心就是考虑用恰当的方法编码状态,保留解决问题需要的全部信息,且尽可能的简单。

其他一些例子和想法:

石子合并

  • 记忆化搜索:适合数学苦手,推不出显示的公式。
  • 记得某次TC 500,我逼急了,直接用字符串编码当前状态,记忆化搜索,居然也过了,之后看题解,很类似石子合并。这也算是我写本文的一个动机吧,只要找到了一个正确的编码,能够将状态重新组织起来,方式可能有很多,甚至这么搞,不卡常数也是可以的。。。
  • 本文只是讨论了如何设计问题的状态。对于优化转移,一些情况下可以使用一些数据结构的支持,一些情况下可以通过四边形不等式,单调队列等,技能暂时没有 get,暂不讨论。

0-1背包问题

  • 动态规划做法,本质上是另一种编码方式。当背包体积为整数且范围不大的时候,这种编码方法是优于暴力枚举的。反过来说,如果背包体积的取值范围很大,这样做相当于把一个一一映射的解空间,映射到一个更大的稀疏的解空间中了,效果可能还不如暴力搜索。

旅行商问题

  • 显然可以直接记录访问顺序,状态空间是 ( O(N!) )。考虑问题的性质,我们只关心当前位置和还可以访问的点集合,于是,可以以( O(N*2^{N}) ) 编码状态。问题的规模少许得到了简化。举这个例子也是因为我个人的看法,“状态压缩”就是可以直接编码集合,本质还是一样的。
原文地址:https://www.cnblogs.com/sweetsc/p/3533473.html