您的位置:首页 > 其它

动态规划

2016-05-14 12:32 260 查看
本文希望对在程序设计竞赛中的动态规划算法做一个有限范围的全面的总结。
从解决问题的角度上来说,把动态规划看成两个部分:
1.使用子空间之间的递推来解决最优问题的高效(时间优化)算法
2.基于子问题解的某些量计算来解决全部空间某个量的困难问题算法
下面详细的分类讨论这两个思路在各种问题中的体现。
一.有向无环图的路径规划和处理问题
    在这里,我们借助具体问题提出动态规划的一些基本概念。阶段和决策。
例1.嵌套矩阵
    有n个矩形,用两个整数a,b描述。给出一系列n个矩形,输出可以实现的最大嵌套的个数。
     我们可以把只用前i个矩形,并且以第i个矩形为结尾的最大嵌套个数看成子问题(这个定义很重要),这个子问题有什么特点呢?首先i=n时的答案就是要求的答案。其次从第i个阶段可以由前i-1个阶段推出。这里的阶段实质是一个二维的定义,包括使用的前i个,和以第i个为结尾。决策就是“是否把第i个矩形放在第k个矩形为结尾的后面”,转移过程是取一系列决策的最大值。
     我们来具体分析一下这个有向无环图。
     从直观上来说,图的每个节点应该是一个矩阵,两个节点之间的有向边则表明可以实现转移。建立了总体数据的有向无环图,我们的目标是找到从若干起始点(入度为0)到若干最终点(出度为0)的最长路径中的最大的一条。同时在每个节点储存,从一系列起始点到该节点的最大路径,这样时间复杂度减少到n^2。
      上述分析过程和动态规划的算法过程在思想上是相同的。本质上是通过储存子问题最优解将最优化问题的搜索过程时间复杂度降低。
现在可以把这个门类下的问题分为三类
      1原问题可以直观建模为有向无环图。
      1.1图上的最短路(最长路与之互补)
      1.2图上的路径条数
      1.3打印路径
      问题列举
      1.4.1立方数之和,找出一个数由多个小于它的数的立方和的分解方案数。
      定义d[i][j]为用不超过i的数的立方和组成j的方案数。
      那么d[i][j]=d[i-1][j-k*i^3]{j-k*i^3>0}
      这里要引入两种方程的递推方法
      第一个是更新法,每进行到一个节点(有向无环图中的),就根据加法原理更新所有它的下一个连接节点(连接对应决策)。对于本题就是
d[i+1][j+k*(i+1)^3]+=d[i][j]{j+k*(i+1)^3<jmax}
当然显得很复杂,但对于其他枚举后继很容易,但寻找它的前驱很困难的问题,这样做是一种思路。
      第二个是追求最低复杂度的递推法
观察这个动态转移方程,可以分析出它的时间复杂度接近n*n*(n^1/3)
然而如果递推方程的某个状态被多次使用,且不具有后效性。就可以通过维护的方法,在每次需要的时候直接得到。
所以这个方程可以改成
d[i][j]=d[i-1][j]+d[i][j-i^3]{j>i^3}
具体分析请读者思考。
       1.4.2用n个火柴可以组成多少个数字
      2多阶段的决策问题(原问题不能直接转化,或者转化过于复杂,但是每解决一层阶段的问题,原问题的解就更完善)
      例3.01背包问题
n种物品,每个物品只有一个,第i种体积为vi,重量为wi。求容量C对应的最大重量。
定义使用前i种物品,在容量为j时对应的最大重量为一个阶段d[i][j],它的决策是是否放入第i个物品。那么状态转移方程
d[i][j]=max(d[i-1][j],d[i-1][j-vi]+wi){j>vi}
如果以容量为节点(最后有可能出现不同节点但容量相等),它的子节点定义为减去某个物品的容量,到子节点的这条边的权重等于该物品的重量,且该物品并未在其父节点之前使用。我们也能建立一个有根树。找到一条由根到叶子的路径并且使边的累积路径权重和最大。
把这个思路递归实现,并且记录每个节点的最大重量,就是动态规划算法的记忆化搜索实现方法。
如果每个物品有无限多个呢。
上述的第二个思路其实更为简单。
对于决策的定义,改为放入第i个物品的个数(可以是0)。
d[i][j]=max(d[i-1][j-k*vi]+k*wi){j-k*vi>0}
二.经典动态规划问题
1最长上升子序列
定义d[i]是以i为结尾的最长上升子序列,d[i]=max(d[j],0)+1 {ai>aj}
由于有判定条件的约束,不能通过维护的方法是时间复杂度降低。时间复杂度为n^2
nlogn的算法请百度。
如果定义d[i]是前i个元素的最长上升子序列,状态转移方程很难写出。
现在从另一种角度来分析这个问题。我们把序列的每一个元素看成一个节点,两个节点之间连接一条有向边当且仅当被指向节点大于上一个节点,且同时原序列中的序号也大于它。我们就可以建立一个有向无环图。所求问题就是最长路径。
2最长公共子序列
定义d[i][j]为长度为i的前i个元素和长度为j的前j个元素的最长公共子序列
a[i]=b[j]
d[i][j]=d[i-1][j-1]+1
a[i]!=b[j]
d[i][j]=max(d[i-1][j],d[i][j-1])
主要思考打印出该序列的方法。
只要我们记录了转移过程,也就是d[i][j]的取得方法就可以,通过递归的方式打印出来。这个思路也能用来打印最长上升子序列。
3最大子序列和
三.集合动态规划
1.指数级别复杂度问题(一般是NPC)
问题1.TSP问题
有n个城市,给出他们之间的距离。从一个城市出发,经过所有城市并回到出发点的最短路径。或者是最小权重和。
假设出发点是城市0。
定义d[i][S]为从城市i出发,经过集合S中的城市,再回到0的最短路径长度
这个状态定义当然是核心。先来考虑它的状态转移方程。
分析决策,我在第i个城市,我的决策就是下一个去S中的某个城市,设为j。
那么可以写出
d[i][S]=min(d[j][S-{j}]+dis(i,j)*w){j属于S}
边界条件d[i][{}]=dis(i,0)*w
在算法实际应用中集合用二进制表示,这样可以方便的使用位运算。当然有些实际问题是三进制,八进制之类的仍然可以转成多个二进制来简化运算。
问题2.给出一个n张扑克牌(不考虑花色)组成的集合,按照斗地主的规则有多少种出牌方法(考虑先后顺序)
定义d[S]为集合S的出牌方法总数
d[S+A]+=d[S]{A为{U-S}(全集除去S)中的可能出牌}。。。更新法
问题3.给出一个长度为x,宽度为y的巧克力。每次可以横着切或纵着切。问是否可以得到面积和为a1,a2,a3..an的许多巧克力。
状态定义为当是否可以把长a宽b的巧克力切割成面积集合S
决策是横着或竖着切(限制条件是切出来的一块是面积集合中的某个)
d[a][S]=d[a-r][b][S-A]{0<r<a,A=r*b}
d[a][b][S]=d[a][b-r][S-A]{0<r<b,A=a*r}
为了简化时间复杂度
注意到d[a][b][S]=d[b][a][S]
且如果a*b!=SUM(S)那么这一项就是0
[b]四.树形dp

给出一个森林(或者是树,本质上没有区别)在某个节点放置灯,使所有边被照亮,且灯尽量少的前提下,被两个灯照亮的边尽可能多。
我们需要对多目标优化问题定义优先度
x=ka+b,a是放灯数,b是两灯照亮边。x是总优化目标,a绝对优先,则k根据题目范围应该取一个很大的值。
使用记忆化搜索解决这个问题很直观
对于某个节点,它的决策有两种,放灯与不放灯。它不放灯必须要求父节点放灯,或者它是根。
这是动态规划的一个方法,把限制条件引入状态,通过增加一个或多个维度来实现。
定义递归函数
dp[i][j]
dp(i,father,isflight)
{
if(dp[i][j]==1)
return dp[i][j]
..........//边界设定
int &ans=dp[i][j]
if(father==-1||isflight==1)//根或者父节点放灯
ans=max(SUM[dp(j,i,0)],SUM[dp(j,i,1)])
else
ans+=dp(j,i,1);
//上述j是i的子节点
}
 
五.轮廓线动态规划
填充棋盘
用1*2的牌填充n*m棋盘的种类数。
把限制条件引入动态规划的典型。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: