无向树的直径(最长的两点间最短距离)

无向树的直径,一个看似不起眼的东西,却牵涉到了许多的问题。
【定义】
什么是无向树的直径?就是最长的两点间最短距离。也就是说,对于某两个点i和j,它们间的最短距离最大。而这就是无向图的直径。
【分析】
一开始我以为是二分,看到“最短距离最大”,我瞬间就想到了“最小值最大”。但是这是错误的思路。
【暴力1:BFS O(n^2+n*m)】
首先,一种暴力的思路,以每个点当做其中的一个点i,用BFS来找到另一个点j,时间复杂度是O(n^2),但是常数很大,非常容易超时。
【暴力2:枚举叶节点+LCA最短路 O(n*log2n+n^2* l og2n)】
但是,很显然的,i和j都必定是叶节点,不然的话,它们的孩子就会比它们拥有更大的相互距离。所以,我们只需要枚举所有的叶节点就可以了,非常简单。不过BFS的时间复杂度中常数的比例太高了,所以说我们不如直接用O(n^2)枚举,接着用LCA求最短的路径。合起来是O(n^2log2n),但是因为常数的关系,似乎和前面的暴力比起来还要略胜一筹。
【补充:LCA求最短路 O(n*log2n)-O(log2n)】
为什么LCA和最短路间有关系呢?因为对于节点(i,j),它们间的最短路径必然是(i-LCA(i,j))和(j-LCA(i,j))这两条路径的合并。也就是说,树上的最短路,必然是先到LCA再到另一个节点的。为什么呢?下面我们用一个很简单的方法证明,其他的路径不会比这条路径优(如果所有的路径都为正的话。如果有负路径的话,就要套用图的最短路了)。
首先,考虑将LCA换为另一个节点u,而如果这个节点在LCA路径(即我们刚才说的那个先到LCA再到另一个节点)的话,那么路径显然是相同的。而如果不是呢?考虑节点u的深度比LCA要小的情况,那么肯定会多经过几条边,而且会有路径的重合,至于深度小的情况,也是一样的。如果深度一样呢?那就更不用讨论了,因为其他的节点都要绕道一个更高层的节点,肯定没有LCA路径更优。
那么,这个路径长度有怎么求呢?考虑(i-LCA(i,j))这种路径,我们可以维护一个“树上前缀和”,或者说是树上每个节点的深度。这很容易求出来,只需要对于每个节点u,则其深度deepth(u)=deepth(father(u))+1。也就是说,每个节点的深度都是其父节点的深度加1。这样的话,就可以只用一次BFS就求出最终的结果了。
【树形DP O(n)】
但是,我们有更好的方法。那就是树形DP。什么是树形DP?首先考虑DP的性质,有一个“无后效性”。而属性就恰好满足这样的“无后效性”。比如说我们要从底向上的话,我们只需要将底层的先逐一DP,接着就可以用很容易的方法,转移上去了!在这里,每一棵子树都相当于一个子问题,接着由于没有环的性质,所以说当前的DP不会影响到之前的情况。因此,我们可以尝试这么一种DP的特殊方法。
对于以每一个点作为根节点的子树,我们完全可以用一个DP求出离root最远的节点,比如说设f[root]就是指以root为根结点的子树的距离最远的节点,那么f[root]=maxlenpos{j,j是root的孩子节点}。那么,在这里我们要求的某两个节点的最长的最短距离的一对节点(i,j)呢?显然也是不难求的。如果要求对于某个root的i和j的话,只需要将i设为离root最远的节点,而j设为离root第二远的节点,然后就同样很容易推上去了。如果设为对于root的(i,j),为di[root]和dj[root],那么di[root]=biggestpos{j,j是root的孩子},dj[root]=secondbiggestpos{j,j是root的孩子}。最终的答案就是len(di[root],dj[root])(在这里root是整棵树的根节点)。那么这个路径的长度应该都可以直接通过LCA来求出来了。因为我们求出的biggestpos和secondbiggestpos就是第一大和第二大的下标,那么其和也显而易见的是最大的。注意,如果有多个最大值,则“第二大”的值也应该是最大值。总之,是在所有{f[j],j是root的孩子}中选择两个和最大的,这可以用一次线性的扫描就做到这一点。而每一个节点最多会被DP一次(DP的性质,再算一遍就浪费了,而由无后效性可知,不需要再算一遍,否则就不能用动态规划了),对孩子节点的扫描也最多只能够对每一个节点最多总共扫一遍,因为每一个节点最多是一个节点的孩子节点(就是它的父亲节点)。
像这样的树形DP,相信肯定还会有一些大用处。下一次,当我们看到一个和树有关的图论问题时,我们就可以考虑用树形DP来解决。比如说树上的一些计数问题(选一系列的符合要求的节点,问选择的方案数量)、规划问题(选一系列的节点,满足某种要求且最优)。

【带一些规约的两次BFS O(n+m+n+m)】
前面说过,我们如果用普通的BFS的话,会有严重的效率问题。但是,我们也看到了另外一个突破口:设最终答案为(i,j),那么当我们知道i的时候,只需要一次BFS就可以知道j了。其的时间复杂度很低,仅仅是读入的规模。
那么问题来了:我们该如何找到这么一个节点i呢?
首先,我们可以做出一个很简单的证明,来证明一个命题:首先设整个点集为V,对于每一对的i,j∈V,不管是由哪一个节点u∈V作为根节点,这对i和j都必定是这棵树中的最长的相互最短距离点对。因为首先,这棵树是一棵无向树,而无向树是一种图,那么我们只从图的方向来出发,图中的两点间的最短路径,在没有相同费用边、负环和0费用的边,那么最短路径是唯一的。又因为在这里的边的长度都是1,那么显而易见的,命题成立(这个命题同时也是上面的树形DP的成立的基础)。
接下来,我们再来一个命题:对于无向树中的任意一个节点,我们的(i,j)中距离较远的一个,比如说设点i是距离根节点较远的一个,那么i到根节点的距离同样也是最远的。我们的证明使用反证法:如果根节点距离i不是最远的,而是距离另外一个节点比如说i'是最远的,那么很显然(i',j)的长度比(i,j)要长。根据我们前面的结果,i和j必然是叶节点,而通过“精心安排”的根节点的位置,可以使得i和j的LCA就是根节点。而就算不是根节点,也是一样的道理。总而言之,可以很容易地看出LEN(i',j)>LEN(i,j)。所以,这和假设LEN(i,j)最大相悖。因此我们必定可以先选定任意一个节点,然后通过一次BFS找到i,再用一次BFS找到j。总体而言,还是非常之简单的。

【最巧妙的方法:看似毫不相关的拓扑排序】
考虑这样的一个过程:不断地删去这棵无向树的“叶节点“(这里的”叶节点“不是严格意义上的叶节点,而是指最外层的节点),然后最终的轮数*2就是这棵树的直径。也就是说,我们只需要一次拓扑排序,即可完成这个过程。我们只需要拓扑排序,即不断删去入度为1的点即可(因为再怎样,节点都有一个父节点,也就是至少都有一个出度)。但是这里要加一个“轮数”的概念,也就是说,同一张有向无环图,在不真正删点之前,所有入度为0的点都属于同一轮。删了之后,不断地再次寻找新的点,那么最终的直径就是轮数*2。
这个代码的正确性给出以下的证明:首先考虑最后所剩下来的所谓“残渣”节点,因为我们的若干轮的删除,最后一轮的删除只可能删除这一个节点。因为树的树形结构,所以说最后一个入度为0的节点必定是这个。根据拓扑排序的思路,这个节点也是最后一个可能被访问的节点。那么,因为最后只剩下这么一个节点,而又通过我们的过程可以知道,以最后的这个节点为根,那么这个根到每一个叶节点的长度都是相等的。所以,又通过我们之前的证明,可以知道(i,j)必定都是叶节点。那么又通过我们现在,每一个叶节点的长度都相同的情况下,那么最终的答案就必然是删除的轮数(同时也是每一个叶节点到根节点的距离)。
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页