树的直径—求法

先给出树的直径的定义:一棵树中任意两点间的路径叫“链”,最长的链称作树的直径。树的直径并没有明确指明是一个距离还是一段路径,其实两者都可以用“树的直径”来称呼。
具体求树的直径我们有两种方法,一种用DP,一种用dfs,它们的时间复杂度都是O(N)。

 

树形DP法

设数组D[i]表示从节点i出发往子树方向走的最长距离,F[i]表示以节点i为转折点的最长链。显然ANS=max{F[i]}。
仔细考虑它们的联系,F[i]=最大的D[i]+次大的D[i]。如果真要记录两个D,代码会复杂很多。再仔细考虑一下实现过程,当在x节点让y去dfs的时候,D[y]最终会得到一个最大深度。在之前D[x]=\max\left\{D[x],D[y]+e[k].c\right\},D[x]中存的是通向另一个子节点的最大深度。它们的关系不有点像最大的D和次大的D吗?只不过这个次大的D不是严格上的次大罢了。但是根据两两间的匹配原则,A-B和B-A是一回事,就是说这两者早晚会是那个最大和次大的关系。
所以在dfs中要这样写:

void dp(int x,int fa)
{
    for(int k=last[x];k;k=e[k].next)
    {
        int y=e[k].y;
        if(y==fa) continue;
        dp(y);
        ans=max(ans,d[x]+d[y]+e[k].c);//这里其实是ans=max(ans, f[x]=max(f[x],d[x]+d[y]+e[k].c) );不过这样的f[x]没有意义,舍弃了
        d[x]=max(d[x],d[y]+e[k].c);
    }
}

两次BFS/DFS

第一次找出一个离x最远的节点,第二次从那个节点开始找一个最深的节点。
可以证明这样找出来的一定是树的直径。不妨不这个点调整到根节点。先证明第一步,第一次DFS出来的一定是直径的一端。假设我们找到了一个最深的点x但它不是直径的一端,假设直径的一端在根节点的另一个子树里,节点记为y,那么显然不成立。因为从y出发最大的向上距离是到根,从根出发最大的向下距离是到x,所以x一定是直径的一端。假设直径的一端l与x处于同一棵根子树,若直径的另一端r也在这棵根子树中,如果l先向上到底lca(l,r),然后拐入r,明显不如x出发到lca(l,r)再到r长,因为x更深。如果l,r不在一棵根子树中,r一定会优先选择更深的x。

int son[maxn],d[maxn];//d记录最大深度,son[x]记录最大深度的那个节点
void dfs(int x,int fa)
{
    vis[x]=T;
    d[x]=0;son[x]=x;
    for(int k=last[x];k;k=e[k].next)
    {
        int y=e[k].y;
        if(y==fa) continue;
        dfs(y);
        if(d[y]+e[k].c>d[x])
        {
            d[x]=d[y]+1;
            son[x]=son[y];
        }
    }
}
int main()
{
    int s,e;
    dfs(1,0);s=son[1];
    dfs(s,0);e=son[s];
}

比较&总结

两次BFS/DFS的做法优于树形DP之处在于它可以求出树上的最长链到底是那条,具体的给出一个起始点S和终止点E,这样在一次遍历后我们可以找出那条实实在在的链到底是什么,相当于求出了方案。但是两次BFS/DFS貌似不能解决带负权边的问题,这时只能用树形DP了。

你可能感兴趣的:(动态规划DP,树形DP,递归/DFS,树的直径)