DAG:有向无环图
1 求整个DAG中的最长路径
问题描述:给定一个有向无环图,求解整个图的所有路径中权值之和最大的那条。
解题
采用动态规划+递归的方法,令dp[i]表示从i号顶点出发能获得的最长路径长度。
需要按照拓扑逆序求解dp数组(即从没有后继结点的结点开始计算),借助递归策略实现拓扑逆序的同等效果
dp[i] = max{dp[j]+length[i-->j]},其中j是i的后继结点
// dp[i]表示从i号顶点出发能获得的最长路径长度, 初始化为0
int DP(int i){
if(dp[i]>0) return dp[i]; // dp[i]已计算得到
for(int j=0;j<n;j++){ // 遍历结点i的所有出边
if(G[i][j]!=INF){
dp[i] = max(dp[i],DP(j)+G[i][j]); // 用DP(j)的方式获取dp[j], 借助递归策略实现拓扑逆序的同等效果
}
}
return dp[i]; // 返回计算完毕的dp[i]
}
由于从出度为0的顶点出发的最长路径长度为0,因此边界为这些顶点的dp值为0。但具体实现中不妨对整个dp数组初始化为0,这样DP函数当前访问的顶点i的出度为0时(第5行的if判断永远为false)就会返回dp[i]=0(以此作为dp的边界),而出度不是0的顶点则会递归求解,递归过程中遇到已经计算过的顶点(第3行的判断为true),则会直接返回对应的dp值,于是从程序逻辑上按照了拓扑逆序列的顺序进行。
2 固定终点,求DAG的最长路径
解题思路
假设规定的终点为T,令dp[i]表示从i号顶点出发到达终点T能获得的最长路径长度。
与第一问的区别
边界。
在第一个问题中没有固定的终点,因此所有出度为0的顶点的dp值为0是递归边界;
但是本问题固定了终点,因此递归边界应当为dp[T]=0。
dp数组每个结点对应的值因初始化为-INF;然后设置一个vis数组表示顶点是否已经被计算(第一个问题中不需要设置vis数组,因为当dp[i]>0时就说明顶点已经被计算)
// G[i][j] 图使用邻接矩阵的方式存储
int DP(int i){
if(vis[i]) return dp[i]; // dp[i]已计算得到
vis[i] = true;
for(int j=0;j<n;j++){ // 遍历i的所有出边
if(G[i][j]!=INF){
dp[i] = max(dp[i],DP(j)+G[i][j]);
}
}
return dp[i]; // 返回计算完毕的dp[i]
}
3 矩形嵌套问题
问题描述
给出n个矩阵的长和宽,定义矩阵的嵌套关系为:如果有两个矩形A和B,其中矩形S的长和宽分别为a、b,矩形B的长和宽分别为c、d,且满足a<c、b<d,或a<d、b<c,则称矩形A可以嵌套于矩形B内。现在要求一个矩形序列,使得这个序列中任意两个相邻的矩形都满足前面的矩形可以嵌套于后一个矩形内,且序列的长度最长。如果有多个这样最长的序列,选择矩形编号序列的字典序最小的那个。
这个例子就是典型的DAG最长路问题。将每个矩形都看成一个顶点,并将嵌套关系视为顶点之间的有向边,边权均为1,于是就可以转换为DAG最长路问题。
4 总结归纳
在大多数的情况下,都可以把动态规划可解的问题看作一个有向无环图DAG,图中的结点就是状态,边就是状态转移的方向,求解问题的顺序就按照DAG的拓扑序进行求解。从这个角度可以辅助理解动态规划。