这题很不错,由于题目是树形,而且状态转移并不复杂,动归的思想可以从这题中很好的体现。所以把这题求解的过程详细的记录下来。
原题 : // 一个有向无环图,只有一个入口和一个出口,插入若干个节点,使得从入口到出口的所有路径长度相同(长度是路径经过的节点的个数)。
// 并分析时空复杂度。
分析 :
题目要求插入若干个点,使得从入口到出口的所有路径长度相同 。
首先将题目表达的更明确些:
设e为图的出口节点,i为图的非出口节点,要使i到e的所有路径长度相同。
直观的感觉可以分两步解决这题:先找出i到e的所有路径中最长的路径长度,再将剩余路径补上一块,使得从i到e的所有路径长度相等。
如果第一步得到解决,那么第二步将变得很简单。所以问题的关键转移到如何解决第一步,即找出i到e的所有路径长度的最大值。
求最值问题,就很容易联想到动归。但如果能找到问题存在子问题,并且子问题间重叠,那么就很有可能可以应用动归了。
动归的第一步,寻找子问题( 即最优子结构 )。
见图:
可以发现 i到e的最大路径长度 = 所有i的子节点到e的最大路径长度 的最大值+1。
好了,找到子问题了,下面来看看能否找出子问题间的重叠。
^_^,找到了,节点j1有两个父节点,要求出j1的两个父节点到e的最大路径长度,都要经过节点j1,即先求出j1到e最大路径长度。
原问题的子问题与子问题的重叠性都找到了,这题可以用动归解决了。
动归的第二步,给出一个递归的方程。
现在来定义问题状态:D[i],表示节点i到e的最大路径长度。
上面的关系可以用递归方程表示:
D[i] = Max{ D[j] } + 1 ( 其中 i != e )
D[e] = 1 ( 出口节点到自身的长度设为1 )
现在可以根据第二步的方程很容易写出动归的程序了,完成解决这题的第一步了。
不过这题并不是要求某节点到出口节点的最大路径,而是要求节点到出口的所有路径长度相同。
这一步比较简单,当求出某节点的所有子节点到出口的最大路径长度后,只需对所有子节点到出口的路径补上一块,使这些路径长度相同即可。
由于这题是树形结构,更适合记忆化搜索,下面给出记忆化搜索的伪代码。
其中 Table[]是备忘录,以免子问题被多次重复计算。
参数i是节点标号,j是i的子节点标号。
int DP( i )
{
// 节点i已求
if( Table[i] != 0 )
return Table[i];
// 节点i未求,从所有子问题求得i
int maxV = 1;
for( all sub nodes of i )
{
maxV = max( maxV, DP( j ) );
}
// 修正从节点i出发的路径长度,使所有路径长度都为maxV
for( all sub nodes of i )
{
Table[j] = maxV;
}
// 求出节点i的下降路径长度
Table[i] = maxV + 1;
return Table[i];
}
最后,计算这题的时空复杂度。