DAG最长路问题
- DAG最长路
- 求整个DAG的最长路径(即不固定起点和终点)
- 固定终点,求DAG的最长路径
- 适用场景
DAG最长路
在图的有关知识中已经了解了DAG就是有向无环图,其中计算最长路(关键路径)的做法非常复杂,这里介绍更简单的方法。
- 求整个DAG的最长路径(即不固定起点和终点)
- 固定终点,求DAG的最长路径。
求整个DAG的最长路径(即不固定起点和终点)
给定一个有向无环图,怎样求解整个图的所有路径中权值之和最大的那条。
令dp[i]表示从i号顶点出发能获得的最长路径长度,这样所有dp[i]的最大值就是整个DAG的最长路径长度。
如何求解dp数组,发现dp[i]表示从i号顶点出发能获得的最长路径长度,如果从i号顶点出发能到达顶点j1、j2、····、jk,而dp[j1],dp[j2],····,dp[jk]都已知,那么就有:
dp[i] = max{ dp[j] + length[i->j] | (i,j)∈E }
显然需要按照逆拓扑序列的顺序来求解dp数组,或者使用递归;其中用邻接矩阵来存储图:
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]);
}
}
return dp[i];//返回计算完毕的dp[i]
}
在具体实现时,对整个dp数组进行初始化为0。 这样如果遇到出度为0的结点会自动返回dp[i]= 0,而如果出度不是0的顶点则会递归求解。
如何知道最长路径具体是哪条呢?
可以开一个int型choice数组记录最长路径上的顶点的后继顶点,使用迭代即可获得每个的后继顶点。如果最终可能有多条最长路径,把choice数组改为vector类型的数组即可。
int DP(int i){
if(dp[i] > 0) return dp[i];
for(int j =0;j<n;j++){
if(G[i][j] != INF){
int temp = DP(j)+G[i][j];//单独计算,避免多次计算
if(temp > dp[i]){
dp[i] = temp;
choice[i] =j;//i号顶点的后继是j
}
}
}
return dp[i];
}
//调用printPath前需要先得到最大的dp[i],然后将i作为路径起点传入
void printPath(int i){
printf("%d",i);
while(choice[i] != -1){//choice数组初始化为-1
i = choice[i]
printf("->%d",i);
}
}
对其他的动态规划问题而言,如果需要得到具体的最优方案,可以采用类似的方法,即记录每次决策所选择的策略,然后再dp数组计算完毕后根据具体情况进行递归或迭代来获取方案。
固定终点,求DAG的最长路径
假设规定的终点为T,那么可以令dp[i]表示从i号顶点出发到达终点T能获得的最长路径长度。
求解dp数组的思想和上述一样,区别在边界的确定上。第一个问题中没有固定终点,那么所有出度为0的顶点的dp值为0是边界,但在这个问题中确定了终点,因此边界为dp[T] = 0。
初始化dp数组为一个负的大数(-INF),来保证“无法到达终点”的含义得以表达;然后设置一个vis数组表示顶点是否已经被计算。
int DP(int i){
if(vis[i]) return dp[i];//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];
}
适用场景
除了关键路径的求解外,可以把一些问题转换成为DAG的最长路,如经典的矩形嵌套问题。
矩形嵌套问题:给出n个矩阵的长和宽,定义矩形的嵌套关系为:如果有两个矩形A和B,其中矩形A的长和宽分别为a,b,矩形B的长和宽分别为c,d,且满足a<c,b<d,或a<d,b<c,则成矩形A可以嵌套于矩形B。现在要求一个矩形序列,使得这个序列中任意两个相邻的矩形都满足前面的矩形可以嵌套于后面一个的矩形内,且序列的长度最长。如果有多个这样的最长序列,选择矩形编号序列的字典序最小的那个。
可以将每个矩形都看成一个顶点,并将嵌套关系视为顶点之间的有向边,边权均为1,这样就可以转换为DAG最长路问题。